From abe3bcb19c4567cf7fab3de38bc07365eaeb70fd Mon Sep 17 00:00:00 2001 From: gobbo_a Date: Mon, 1 May 2023 11:28:04 +0200 Subject: [PATCH] --- config/.DS_Store | Bin 8196 -> 8196 bytes config/config.properties | 106 +- config/devices.properties | 23 +- config/diffcalc/.DS_Store | Bin 0 -> 6148 bytes config/jcae.properties | 18 +- config/jcae.properties.back.properties | 12 + config/plugins.properties | 22 +- config/preferences.bin | Bin 0 -> 3853 bytes config/preferences.json | 137 + config/scicat.properties | 4 +- config/sessions.properties | 6 +- config/settings.properties | 11 +- config/setup.properties | 6 +- config/tasks.properties | 10 +- config/variables.properties | 9 +- config/xscan.properties | 11 + devices/.DS_Store | Bin 0 -> 18436 bytes devices/0000.tiff.properties | 20 + devices/0001.tiff.properties | 20 + devices/0002.tiff.properties | 20 + devices/0003.tiff.properties | 20 + ...5808_simulation_snapshot.h5.png.properties | 20 + ...2805_simulation_snapshot.h5.png.properties | 20 + devices/A1.properties | 3 + devices/A2.properties | 3 + devices/A3.properties | 3 + devices/A4.properties | 3 + devices/A5.properties | 3 + devices/B1.properties | 3 + devices/B2.properties | 3 + devices/B3.properties | 3 + devices/B4.properties | 3 + devices/B5.properties | 3 + devices/C1.properties | 4 +- devices/C2.properties | 4 +- devices/C3.properties | 3 + devices/C4.properties | 3 + devices/C5.properties | 3 + devices/CameraServer.properties | 25 + devices/CurrentCamera.properties | 50 +- devices/D1.properties | 3 + devices/D2.properties | 3 + devices/D3.properties | 3 + devices/D4.properties | 3 + devices/D5.properties | 3 + devices/E1.properties | 3 + devices/E2.properties | 3 + devices/E3.properties | 3 + devices/E4.properties | 3 + devices/E5.properties | 3 + devices/F1.properties | 3 + devices/F2.properties | 3 + devices/F3.properties | 3 + devices/F4.properties | 3 + devices/F5.properties | 3 + devices/R1.properties | 3 + devices/R2.properties | 3 + devices/R3.properties | 3 + devices/R4.properties | 3 + devices/R5.properties | 3 + devices/RoomTemperatureBasePlate.properties | 1 + ...610_o_SS_an5_10x10_20s_0636.tif.properties | 20 + devices/Time.properties | 17 +- devices/bi.properties | 25 + devices/bs.properties | 3 +- devices/ca.properties | 2 +- devices/cam_server.properties | 40 +- devices/chi.properties | 21 +- devices/cm1.properties | 13 +- devices/cs.properties | 49 +- devices/curve_fitting.png.properties | 20 + devices/cv.properties | 13 +- devices/delta.properties | 21 +- devices/dispatcher.properties | 3 +- devices/dp1.properties | 23 +- devices/eiger.properties | 17 + devices/energy.properties | 17 +- devices/eta.properties | 3 +- devices/gamma.properties | 21 +- devices/histo.properties | 9 +- devices/image.properties | 36 +- devices/m1.properties | 25 +- devices/m2.properties | 3 +- devices/m3.properties | 17 + devices/m4.properties | 17 + devices/m5.properties | 17 + devices/m6.properties | 17 + devices/manip.properties | 17 +- devices/maser.properties | 10 + devices/master.properties | 34 +- devices/motor.properties | 25 +- devices/motor2.properties | 37 +- devices/mt.properties | 13 +- devices/mt1.properties | 13 +- devices/mu.properties | 21 +- devices/p1.properties | 17 +- devices/pe.properties | 19 +- devices/phi.properties | 21 +- devices/pkh1mot.properties | 16 + devices/proc_data.properties | 25 + devices/proc_image.properties | 25 + devices/pv.properties | 19 +- devices/sc.properties | 67 +- devices/tab.properties | 29 +- plugins/.DS_Store | Bin 0 -> 16388 bytes plugins/Align_ComplexEdgeFiltering.java | 154 + plugins/Align_ComputeShifts.java | 728 +++ plugins/Align_ComputeShifts2.java | 701 +++ plugins/Align_TranslationFilter.java | 115 + plugins/Align_TranslationFilter2.java | 91 + plugins/EnergyScan.form | 1176 ++--- plugins/MXSC-1.15.0.jar | Bin 0 -> 292171 bytes plugins/ManipulatorScan.form | 11 + plugins/ManipulatorScan.java | 87 +- plugins/PanelPlugin.form | 109 +- plugins/PanelPlugin.java | 119 +- plugins/README | 5 + plugins/Regine.form | 3 + plugins/Regine.java | 2 + plugins/SIStem.form | 395 ++ plugins/SIStem.java | 813 +++ plugins/ScreenPanel.java | 24 +- plugins/ScreenPanel1.form | 960 ++++ plugins/ScreenPanel1.java | 3223 ++++++++++++ plugins/ScreenPanel11.form | 1347 +++++ plugins/ScreenPanel11.java | 4495 +++++++++++++++++ plugins/ScreenPanel9.form | 1 - plugins/ScreenPanel9.java | 23 +- plugins/ShiftsIO.java | 109 + plugins/SpinnerLayoutTest.java | 2 +- plugins/Standard.java | 6 + plugins/TstProc.form | 28 + plugins/TstProc.java | 195 + plugins/resources/.DS_Store | Bin 0 -> 6148 bytes plugins/sc.form | 41 + plugins/sc.java | 91 + script/.DS_Store | Bin 0 -> 30724 bytes script/20150418_0012_Tb_hyst_plus.xml | 24 +- ...RF_otf_200ms_17200eV_XY_tisbe_Cu_HR2um.xml | 171 + script/52_ParametersAndReturn.py | 23 + script/Correlation/Correlation.py | 4 +- script/ManipulatorScan.py | 6 +- script/ManualScan.py | 43 + script/Regine.py | 9 +- script/ShellCommand.py | 73 + script/StateCAS.py | 50 + script/Test.xml | 41 - script/Test3.xml | 46 + script/Test4.xml | 24 + script/Test4b.xml | 19 + script/Test4c.xml | 17 + script/Test4e.xml | 29 + script/Test5.xml | 5 + script/TriggerScan.py | 19 +- script/align/Demo2D.py | 41 + script/align/SimpleDemo.py | 33 + script/bsread_camera.py | 11 +- script/calc.py | 1 + script/cpy/BugMonitor.py | 2 + script/cpy/Device.py | 170 + script/cpy/ManualScan.py | 43 + script/cpy/Signal.py | 14 + script/cpy/TestBlueskyPause.py | 48 + script/cpy/TestJProxy.py | 75 + script/cpy/TestJProxy2.py | 68 + script/cpy/TestJepError.py | 97 + script/cpy/bluesky.py | 178 + script/cpy/bluesky2.py | 30 + script/cpy/local.py | 487 ++ script/cpy/startup_jep.py | 2796 ++++++++++ script/cpy/testJepScan.py | 30 + script/cpy/xxx.py | 115 + script/cpython/gfitoff.py | 30 + script/cpython/linfit.py | 32 + script/cpython/test.py | 34 + script/cpython/test2.py | 14 + script/diffcalc_test/.DS_Store | Bin 0 -> 6148 bytes script/eiger.py | 323 ++ script/imaging/sim.py | 642 +++ script/jep/testdevs.py | 45 + script/local.py | 222 +- script/otf.py | 103 + script/outupdate.py | 2 +- script/queues/mxas.que | 1 + script/queues/q5.que | 1 + script/queues/q6.que | 1 + script/queues/qt.que | 1 + script/queues/tst.que | 1 + script/runotf.py | 30 + script/scitest/.DS_Store | Bin 0 -> 6148 bytes script/templates/Scan2D.py | 70 + script/test/.DS_Store | Bin 0 -> 6148 bytes .../test/2022_0812_092647_XAS_V_L1677_CN.xml | 759 +++ script/test/BugJep2.py | 22 + script/test/BugPhenix.xml | 733 +++ script/test/BugSuperXAS.xml | 249 + script/test/BugSuperXASOrig.xml | 250 + script/test/DirectCamserverStream.py | 6 + script/test/LoadIJStack.py | 8 + script/test/TSTIMGSIM3.py | 32 + script/test/Test2DCont.py | 14 + script/test/TestArrayTestStats.py | 24 + script/test/TestAthosCamerasChannel.py | 10 + script/test/TestCPython.py | 47 + script/test/TestCmd.py | 1 + script/test/TestCompositeScan.py | 31 + script/test/TestConfig.py | 24 + script/test/TestData.py | 28 +- script/test/TestDataToImage.py | 14 + script/test/TestDiscretePositionerSelector.py | 24 + script/test/TestImgMeasure.py | 6 + script/test/TestIndexReadback.py | 31 + script/test/TestJEP.py | 21 + script/test/TestLayout.py | 43 +- script/test/TestMaster.py | 7 + script/test/TestMaster2.py | 3 + script/test/TestMatrixPlotRenderer.py | 4 + script/test/TestMonit.py | 23 + script/test/TestMonit.txt | 9 + script/test/TestMonitor.py | 125 + script/test/TestMultiDimens.py | 44 + script/test/TestParallelJEP.py | 30 + script/test/TestPipelineStream.py | 32 + script/test/TestSnapArray.py | 8 + script/test/TestSpectrum.py | 149 + script/test/TestStreamDemo.py | 15 + script/test/TestStreamNotFixed.py | 33 +- script/test/TestStrip.py | 21 + script/test/TestTablePlot.py | 2 + script/test/TestTerminal.py | 24 + script/test/TestVscan.py | 2 + script/test/TstImgSim.py | 43 + script/test/TstImgSim2.py | 44 + script/test/VERSI_BEAMSTOP_SCAN.xml | 47 + script/test/calc.py | 1 + script/test/inspect.py | 3128 ++++++++++++ script/test/t.py | 7 + script/test/test1.py | 7 +- script/test/test14.xml | 12 +- script/test/test2.xml | 6 +- script/test/test29.py | 2 +- script/test/test2v.xml | 46 + script/test/test6v.xml | 25 + script/test/test7.xml | 7 +- script/test/{test9.xml => test8v.xml} | 15 +- script/test/testParalelSeq.py | 41 + .../test_bug_header_reserve_msg_allocator.py | 24 + script/test/test_cont.xml | 15 + script/test/test_function_script.xml | 29 + script/test/test_loop_caget.py | 11 + script/test/test_manip.xml | 24 + script/test/test_receive_sender.py | 19 + script/test/test_roi.py | 20 + script/test/test_sender.py | 77 + script/test/testbug.xml | 250 + script/test/testempty.xml | 5 + script/test/testsettling.py | 9 + script/test/testx.xml | 9 +- script/test/{test1.xml => testy.xml} | 25 +- script/test/x.xml | 23 + script/test/xxx.xml | 14 +- script/test/y.xml | 23 + script/test2.xml | 18 +- script/ttt.xml | 20 +- script/www.xml | 1 + 265 files changed, 28663 insertions(+), 1295 deletions(-) create mode 100644 config/diffcalc/.DS_Store mode change 100755 => 100644 config/jcae.properties create mode 100755 config/jcae.properties.back.properties create mode 100644 config/preferences.bin create mode 100644 config/preferences.json create mode 100644 config/xscan.properties create mode 100644 devices/.DS_Store create mode 100644 devices/0000.tiff.properties create mode 100644 devices/0001.tiff.properties create mode 100644 devices/0002.tiff.properties create mode 100644 devices/0003.tiff.properties create mode 100644 devices/20221027_155808_simulation_snapshot.h5.png.properties create mode 100644 devices/20221027_162805_simulation_snapshot.h5.png.properties create mode 100644 devices/A1.properties create mode 100644 devices/A2.properties create mode 100644 devices/A3.properties create mode 100644 devices/A4.properties create mode 100644 devices/A5.properties create mode 100644 devices/B1.properties create mode 100644 devices/B2.properties create mode 100644 devices/B3.properties create mode 100644 devices/B4.properties create mode 100644 devices/B5.properties create mode 100644 devices/C3.properties create mode 100644 devices/C4.properties create mode 100644 devices/C5.properties create mode 100644 devices/CameraServer.properties create mode 100644 devices/D1.properties create mode 100644 devices/D2.properties create mode 100644 devices/D3.properties create mode 100644 devices/D4.properties create mode 100644 devices/D5.properties create mode 100644 devices/E1.properties create mode 100644 devices/E2.properties create mode 100644 devices/E3.properties create mode 100644 devices/E4.properties create mode 100644 devices/E5.properties create mode 100644 devices/F1.properties create mode 100644 devices/F2.properties create mode 100644 devices/F3.properties create mode 100644 devices/F4.properties create mode 100644 devices/F5.properties create mode 100644 devices/R1.properties create mode 100644 devices/R2.properties create mode 100644 devices/R3.properties create mode 100644 devices/R4.properties create mode 100644 devices/R5.properties create mode 100644 devices/RoomTemperatureBasePlate.properties create mode 100644 devices/SS_20220610_o_SS_an5_10x10_20s_0636.tif.properties create mode 100644 devices/bi.properties create mode 100644 devices/curve_fitting.png.properties create mode 100644 devices/eiger.properties create mode 100644 devices/m3.properties create mode 100644 devices/m4.properties create mode 100644 devices/m5.properties create mode 100644 devices/m6.properties create mode 100644 devices/maser.properties create mode 100644 devices/pkh1mot.properties create mode 100644 devices/proc_data.properties create mode 100644 devices/proc_image.properties create mode 100644 plugins/.DS_Store create mode 100644 plugins/Align_ComplexEdgeFiltering.java create mode 100644 plugins/Align_ComputeShifts.java create mode 100644 plugins/Align_ComputeShifts2.java create mode 100644 plugins/Align_TranslationFilter.java create mode 100644 plugins/Align_TranslationFilter2.java create mode 100644 plugins/MXSC-1.15.0.jar create mode 100644 plugins/README create mode 100644 plugins/SIStem.form create mode 100644 plugins/SIStem.java create mode 100755 plugins/ScreenPanel1.form create mode 100755 plugins/ScreenPanel1.java create mode 100644 plugins/ScreenPanel11.form create mode 100644 plugins/ScreenPanel11.java create mode 100644 plugins/ShiftsIO.java create mode 100644 plugins/Standard.java create mode 100644 plugins/TstProc.form create mode 100644 plugins/TstProc.java create mode 100644 plugins/resources/.DS_Store create mode 100644 plugins/sc.form create mode 100644 plugins/sc.java create mode 100644 script/.DS_Store create mode 100644 script/20210614181250_XRD_XRF_otf_200ms_17200eV_XY_tisbe_Cu_HR2um.xml create mode 100644 script/52_ParametersAndReturn.py create mode 100644 script/ManualScan.py create mode 100644 script/ShellCommand.py create mode 100644 script/StateCAS.py create mode 100644 script/Test3.xml create mode 100644 script/Test4.xml create mode 100644 script/Test4b.xml create mode 100644 script/Test4c.xml create mode 100644 script/Test4e.xml create mode 100644 script/Test5.xml create mode 100644 script/align/Demo2D.py create mode 100644 script/align/SimpleDemo.py create mode 100644 script/cpy/BugMonitor.py create mode 100644 script/cpy/Device.py create mode 100644 script/cpy/ManualScan.py create mode 100644 script/cpy/Signal.py create mode 100644 script/cpy/TestBlueskyPause.py create mode 100644 script/cpy/TestJProxy.py create mode 100644 script/cpy/TestJProxy2.py create mode 100644 script/cpy/TestJepError.py create mode 100644 script/cpy/bluesky.py create mode 100644 script/cpy/bluesky2.py create mode 100644 script/cpy/local.py create mode 100644 script/cpy/startup_jep.py create mode 100644 script/cpy/testJepScan.py create mode 100644 script/cpy/xxx.py create mode 100644 script/cpython/gfitoff.py create mode 100644 script/cpython/linfit.py create mode 100644 script/cpython/test.py create mode 100644 script/cpython/test2.py create mode 100644 script/diffcalc_test/.DS_Store create mode 100644 script/eiger.py create mode 100644 script/imaging/sim.py create mode 100644 script/jep/testdevs.py create mode 100644 script/otf.py create mode 100644 script/queues/mxas.que create mode 100644 script/queues/q5.que create mode 100644 script/queues/q6.que create mode 100644 script/queues/qt.que create mode 100644 script/queues/tst.que create mode 100644 script/runotf.py create mode 100644 script/scitest/.DS_Store create mode 100644 script/templates/Scan2D.py create mode 100644 script/test/.DS_Store create mode 100644 script/test/2022_0812_092647_XAS_V_L1677_CN.xml create mode 100644 script/test/BugJep2.py create mode 100644 script/test/BugPhenix.xml create mode 100644 script/test/BugSuperXAS.xml create mode 100644 script/test/BugSuperXASOrig.xml create mode 100644 script/test/DirectCamserverStream.py create mode 100644 script/test/LoadIJStack.py create mode 100644 script/test/TSTIMGSIM3.py create mode 100644 script/test/Test2DCont.py create mode 100644 script/test/TestArrayTestStats.py create mode 100644 script/test/TestAthosCamerasChannel.py create mode 100644 script/test/TestCPython.py create mode 100644 script/test/TestCmd.py create mode 100644 script/test/TestCompositeScan.py create mode 100644 script/test/TestConfig.py create mode 100644 script/test/TestDataToImage.py create mode 100644 script/test/TestDiscretePositionerSelector.py create mode 100644 script/test/TestImgMeasure.py create mode 100644 script/test/TestIndexReadback.py create mode 100644 script/test/TestJEP.py create mode 100644 script/test/TestMaster.py create mode 100644 script/test/TestMaster2.py create mode 100644 script/test/TestMatrixPlotRenderer.py create mode 100644 script/test/TestMonit.py create mode 100644 script/test/TestMonit.txt create mode 100644 script/test/TestMonitor.py create mode 100644 script/test/TestMultiDimens.py create mode 100644 script/test/TestParallelJEP.py create mode 100644 script/test/TestPipelineStream.py create mode 100644 script/test/TestSnapArray.py create mode 100644 script/test/TestSpectrum.py create mode 100644 script/test/TestStreamDemo.py create mode 100644 script/test/TestStrip.py create mode 100644 script/test/TestTablePlot.py create mode 100644 script/test/TestTerminal.py create mode 100644 script/test/TestVscan.py create mode 100644 script/test/TstImgSim.py create mode 100644 script/test/TstImgSim2.py create mode 100644 script/test/VERSI_BEAMSTOP_SCAN.xml create mode 100644 script/test/inspect.py create mode 100644 script/test/t.py create mode 100644 script/test/test2v.xml create mode 100644 script/test/test6v.xml rename script/test/{test9.xml => test8v.xml} (72%) mode change 100755 => 100644 create mode 100644 script/test/testParalelSeq.py create mode 100644 script/test/test_bug_header_reserve_msg_allocator.py create mode 100644 script/test/test_cont.xml create mode 100644 script/test/test_function_script.xml create mode 100644 script/test/test_loop_caget.py create mode 100644 script/test/test_manip.xml create mode 100644 script/test/test_receive_sender.py create mode 100644 script/test/test_roi.py create mode 100644 script/test/test_sender.py create mode 100644 script/test/testbug.xml create mode 100644 script/test/testempty.xml create mode 100644 script/test/testsettling.py rename script/test/{test1.xml => testy.xml} (56%) mode change 100755 => 100644 create mode 100644 script/test/x.xml create mode 100644 script/test/y.xml diff --git a/config/.DS_Store b/config/.DS_Store index 1daa08eeddd1734e74648d082e22b3d39df533d5..29eb6429e611ce8771204d2ac581bcb5444c97d3 100644 GIT binary patch delta 43 zcmZp1XmQxkF3c!DxkFfvi$RYei6N08nIU`g1mSR&&Fm82SSBY5i*EiT@`(um5-1H$ delta 38 ucmZp1XmQxkF3ic#kj0S9kjRj_d4q5`%ftqS&Fm82ST@UwzG9l#APWHI!VF6Q diff --git a/config/config.properties b/config/config.properties index 914b94b..ef23751 100755 --- a/config/config.properties +++ b/config/config.properties @@ -1,48 +1,72 @@ -#Tue Mar 30 11:55:18 CEST 2021 +#Mon May 01 11:27:46 CEST 2023 +XScanAppendSuffix=true +XScanCrlogicAbortable=true +XScanCrlogicChannel=null +XScanCrlogicIoc=null +XScanCrlogicPrefix=null +XScanCrlogicSimulated=false +XScanMoveTimeout=600 autoSaveScanData=true -simulation=false commandExecutionEvents=true -logDaysToLive=50 +createSessionFiles=false +dataLayout=default +dataPath={data}/{year}_{month}/{date}/{date}_{time}_{name} +dataProvider=h5 +dataScanFlushRecords=true +dataScanLazy=false +dataScanLazyTableCreation=false +dataScanPreserveTypes=false +dataScanReleaseRecords=false dataScanSaveOutput=false -handleSessions=true -userAuthenticator=ch.psi.pshell.security.LdapAuthenticator | ldap\://d.psi.ch | d.psi.ch | users.psi -logLevelConsole=Off -scanStreamerPort=5563 dataScanSaveScript=false -dataScanSaveSetpoints=false +dataScanSaveSetpoints=true +dataServerPort=5573 +dataTransferHost= +dataTransferMode=Off +dataTransferPath=~/test/transfer +dataTransferUser=gobbo_a +depthDimension=0 +disableDataFileLogs=false +disableEmbeddedAttributes=true +fdaSerialization=true +filePermissionsConfig=Default +filePermissionsData=Default +filePermissionsLogs=Protected +filePermissionsScripts=Default +generateCommandExecutionEvents=true +handleSessions=true +hideServerMessages=false +hostName= +instanceName=Dev +logDaysToLive=50 +logLevel=Fine +logLevelConsole=Off +logPath={logs}/{date}_{time} +noBytecodeFiles=false +notificationLevel=User notifiedTasks=Test1,TestDate, xxx parallelInitialization=false -dataTransferPath=~/test/transfer -saveConsoleSessionFiles=false -dataTransferHost= -versionTrackingManual=true -dataTransferMode=Off -hostName= -userManagement=true -instanceName=Dev -disableEmbeddedAttributes=true -dataServerPort=5573 -hideServerMessages=false -serverPort=8080 -versionTrackingEnabled=true -dataPath={data}/{year}_{month}/{date}/{date}_{time}_{name} -serverEnabled=false -dataScanReleaseRecords=false -depthDimension=0 -dataScanPreserveTypes=false -logLevel=Fine -dataScanFlushRecords=false -logPath={logs}/{date}_{time} -dataLayout=default -disableDataFileLogs=false -sessionHandling=On -generateCommandExecutionEvents=true -terminalEnabled=false -notificationLevel=Off -terminalPort=0 -dataTransferUser=gobbo_a -createSessionFiles=false -versionTrackingLogin={context}/svcusr-hlapp_robot -versionTrackingRemote=git@git.psi.ch\:pshell_config/dev.git -dataProvider=h5 saveCommandStatistics=true +saveConsoleSessionFiles=false +scanStreamerPort=5563 +serverEnabled=false +serverPort=8080 +sessionHandling=On +simulation=false +terminalEnabled=false +terminalPort=0 +userAuthenticator=ch.psi.pshell.security.LdapAuthenticator | ldap\://d.psi.ch | d.psi.ch | users.psi +userManagement=true +versionTrackingEnabled=true +versionTrackingLogin={context}/svcusr-hlapp_robot +versionTrackingManual=true +versionTrackingRemote=git@git.psi.ch\:pshell_config/dev.git +xScanUsesFDASerialization=true +xscanAppendSuffix=true +xscanContinuousUpdate=false +xscanCrlogicAbortable=true +xscanCrlogicChannel=null +xscanCrlogicIoc=null +xscanCrlogicPrefix=null +xscanCrlogicSimulated=false +xscanMoveTimeout=600 diff --git a/config/devices.properties b/config/devices.properties index 37bb6cd..4d2fec6 100755 --- a/config/devices.properties +++ b/config/devices.properties @@ -1,14 +1,18 @@ +$det=ch.psi.pshell.epics.AreaDetector|13SIM1||1000|true +$bc=ch.psi.pshell.epics.AreaDetector|X09DA-FE-BEAMSZX|||true +bi=ch.psi.pshell.imaging.CameraSource|bc||-1000| sp=ch.psi.pshell.serial.SerialPortDevice|||| $pol_offset=ch.psi.pshell.epics.ChannelDouble|TEST|||false -$det=ch.psi.pshell.epics.AreaDetector|13SIM1|||false #det2=ch.psi.pshell.epics.AreaDetector|CCCC||| -image=ch.psi.pshell.imaging.CameraSource|det||-500|false +image=ch.psi.pshell.imaging.CameraSource|det|||true #image2=ch.psi.pshell.imaging.CameraSource|det2||| dispatcher=ch.psi.pshell.bs.Provider|tcp://localhost:9999||| bs=ch.psi.pshell.bs.Provider|tcp://SFTEST-CVME-DBPM1:9000||| #cam_server_local=ch.psi.pshell.bs.PipelineServer|localhost:8889|||true cam_server=ch.psi.pshell.bs.PipelineServer|localhost:8889|||true -#ts1=ch.psi.pshell.epics.GenericArray|TESTIOC:TESTWF2:MyWF||| +cs=ch.psi.pshell.bs.CameraServer|localhost:8888|||true +pip=ch.psi.pshell.camserver.PipelineStream|localhost:8889 simulation_sp|||true +#ts1=ch.psi.pshell.epics.GenericArray|TESTIOC:TESTWF2:MyWF|||false #monit_cam=ch.psi.pshell.imaging.MjpegSource|http://axis-accc8e9cc87b.psi.ch/axis-cgi/mjpg/video.cgi?camera=2 reopen||-200|false #tststr=ch.psi.pshell.epics.ChannelString|TESTIOC:TESTSINUS:SinCalc||| #stream=ch.psi.pshell.bs.Stream| #false:boolean||| @@ -56,7 +60,7 @@ dp=ch.psi.pshell.epics.DiscretePositioner|TESTIOC:CMD|||true #dev=ch.psi.pshell.device.DummyRegister|6||| #dev2=ch.psi.pshell.device.MyDevice|||| #$vhq=ch.psi.pshell.epics.ChannelDouble|T-MMDV5:IST:2 6|Read|| -#outc=ch.psi.pshell.epics.ChannelDouble|TESTIOC:TESTCALCOUT:Output|||true +outi=ch.psi.pshell.epics.ChannelInteger|TESTIOC:TESTCALCOUT:Output|||true #inpc=ch.psi.pshell.epics.ChannelDouble|TESTIOC:TESTCALCOUT:Input|||true out=ch.psi.pshell.epics.ChannelDouble|TESTIOC:TESTCALCOUT:Output 6|||true #outx=ch.psi.pshell.epics.ProcessVariable|TESTIOC:TESTCALCOUT:Output|||true @@ -72,7 +76,7 @@ arr1=ch.psi.pshell.epics.ChannelIntegerArray|TESTIOC:TESTWF2:MyWF -1 false||| mt=ch.psi.pshell.epics.GenericMatrix|TESTIOC:TESTWF2:MyWF 2 3 false||| mt1=ch.psi.pshell.epics.GenericMatrix|TESTIOC:TESTWF2:MyWF 3 2 False None [i||| as1=ch.psi.pshell.imaging.RegisterArraySource|arr1||| -cm1=ch.psi.pshell.epics.ChannelIntegerMatrix|TESTIOC:TESTWF2:MyWF 3 3 false||| +cm1=ch.psi.pshell.epics.ChannelIntegerMatrix|TESTIOC:TESTWF2:MyWF 3 2 false||| pv=ch.psi.pshell.epics.ProcessVariable|TESTIOC:TESTCALCOUT:Input|||true shutter=ch.psi.pshell.epics.BinaryPositioner|TESTIOC:TESTBO:MyBO TESTIOC:TESTBO:MyBO|||true $motor=ch.psi.pshell.epics.Motor|MTEST-GOBBO:MOT1|||true @@ -93,7 +97,7 @@ cv=ch.psi.pshell.epics.ControlledVariable|TESTIOC:TESTCALCOUT:Input TESTIOC:TEST #tcp=ch.psi.pshell.serial.TcpDevice|127.0.0.1:5554||| #beeper=Beeper|||| #imgbis=ch.psi.pshell.imaging.CameraSource|det||-500| -#sc=ch.psi.pshell.imaging.CameraSource|scienta||-500| +sc=ch.psi.pshell.imaging.CameraSource|scienta||-500| tst=ch.psi.pshell.imaging.FileSource|/Users/gobbo_a/Pictures/rsz_spine_pin.png||| ca=ch.psi.pshell.imaging.ColormapAdapter|tst||-500| #tst2=ch.psi.pshell.imaging.FileSource|C:\\Users\\gobbo_a\\Pictures\\tst.png||-500| @@ -101,8 +105,8 @@ ca=ch.psi.pshell.imaging.ColormapAdapter|tst||-500| #rec=ch.psi.pshell.detector.Receiver|tcp://127.0.0.1:5444|||true #rec2=ch.psi.pshell.detector.Receiver|tcp://127.0.0.1:5555||-500|false #pvt=ch.psi.pshell.epics.ProcessVariable|TESTIOC:TESTCALCOUT:Input|Read|| -#$master=ch.psi.pshell.modbus.ModbusTCP|127.0.0.1||| -#ai=ch.psi.pshell.modbus.AnalogOutput|master 0||| +#$tcp_master=ch.psi.pshell.modbus.ModbusTCP|127.0.0.1||| +#ai=ch.psi.pshell.modbus.AnalogOutput|tcp_master 0||| #cache=ch.psi.pshell.device.RegisterCache|sin||| #$scaler=ch.psi.pshell.epics.Scaler|SCALER||| #testpos=ch.psi.pshell.device.DummyPositioner||||true @@ -129,6 +133,7 @@ phi=ch.psi.pshell.device.DummyMotor||||true #fourc=ch.psi.pshell.device.MotorGroupBase|delta eta chi phi||| fourcv=ch.psi.pshell.device.MotorGroupBase|mu delta gamma eta||| energy=ch.psi.pshell.device.DummyPositioner||||true -gch=ch.psi.pshell.epics.GenericChannel|TESTIOC:TESTSINUS:SinCalc||| +gch=ch.psi.pshell.epics.GenericChannel|TESTIOC:TESTSINUS:SinCalc false||| garr=ch.psi.pshell.epics.GenericArray|TESTIOC:TESTWF2:MyWF -1 False||| darr=ch.psi.pshell.epics.ChannelDoubleArray|TESTIOC:TESTWF2:MyWF||| +master=ch.psi.pshell.device.MasterPositioner|mu delta gamma eta||| diff --git a/config/diffcalc/.DS_Store b/config/diffcalc/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..127c5c6ecc2228e2e83ceef8cd3742a9896b0e9e GIT binary patch literal 6148 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 literal 0 HcmV?d00001 diff --git a/devices/0000.tiff.properties b/devices/0000.tiff.properties new file mode 100644 index 0000000..ab681c1 --- /dev/null +++ b/devices/0000.tiff.properties @@ -0,0 +1,20 @@ +#Mon Nov 07 11:49:02 CET 2022 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/0001.tiff.properties b/devices/0001.tiff.properties new file mode 100644 index 0000000..f7d13ee --- /dev/null +++ b/devices/0001.tiff.properties @@ -0,0 +1,20 @@ +#Thu Sep 29 09:20:02 CEST 2022 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/0002.tiff.properties b/devices/0002.tiff.properties new file mode 100644 index 0000000..6bf19f6 --- /dev/null +++ b/devices/0002.tiff.properties @@ -0,0 +1,20 @@ +#Thu Sep 29 09:21:24 CEST 2022 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/0003.tiff.properties b/devices/0003.tiff.properties new file mode 100644 index 0000000..b53cee4 --- /dev/null +++ b/devices/0003.tiff.properties @@ -0,0 +1,20 @@ +#Thu Sep 29 16:50:04 CEST 2022 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/20221027_155808_simulation_snapshot.h5.png.properties b/devices/20221027_155808_simulation_snapshot.h5.png.properties new file mode 100644 index 0000000..a93f370 --- /dev/null +++ b/devices/20221027_155808_simulation_snapshot.h5.png.properties @@ -0,0 +1,20 @@ +#Thu Oct 27 16:24:34 CEST 2022 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/20221027_162805_simulation_snapshot.h5.png.properties b/devices/20221027_162805_simulation_snapshot.h5.png.properties new file mode 100644 index 0000000..d9982bb --- /dev/null +++ b/devices/20221027_162805_simulation_snapshot.h5.png.properties @@ -0,0 +1,20 @@ +#Thu Oct 27 16:28:20 CEST 2022 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/A1.properties b/devices/A1.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/A1.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/A2.properties b/devices/A2.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/A2.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/A3.properties b/devices/A3.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/A3.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/A4.properties b/devices/A4.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/A4.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/A5.properties b/devices/A5.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/A5.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/B1.properties b/devices/B1.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/B1.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/B2.properties b/devices/B2.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/B2.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/B3.properties b/devices/B3.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/B3.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/B4.properties b/devices/B4.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/B4.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/B5.properties b/devices/B5.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/B5.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/C1.properties b/devices/C1.properties index 9f4654d..8ec6c1d 100644 --- a/devices/C1.properties +++ b/devices/C1.properties @@ -1,6 +1,8 @@ -#Fri Mar 19 09:06:16 CET 2021 +#Thu Mar 10 13:02:31 CET 2022 unit= +detection=null offset=10.0 precision=0 sign_bit=0 scale=4.0 +disabled=false diff --git a/devices/C2.properties b/devices/C2.properties index 564dd2e..c8d8a89 100644 --- a/devices/C2.properties +++ b/devices/C2.properties @@ -1,6 +1,8 @@ -#Fri Mar 19 09:06:16 CET 2021 +#Thu Mar 10 13:02:31 CET 2022 unit= +detection=null offset=20.0 precision=0 sign_bit=0 scale=1.0 +disabled=false diff --git a/devices/C3.properties b/devices/C3.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/C3.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/C4.properties b/devices/C4.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/C4.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/C5.properties b/devices/C5.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/C5.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/CameraServer.properties b/devices/CameraServer.properties new file mode 100644 index 0000000..7fb17af --- /dev/null +++ b/devices/CameraServer.properties @@ -0,0 +1,25 @@ +#Mon Nov 07 19:00:17 CET 2022 +colormap=Grayscale +colormapAutomatic=false +colormapLogarithmic=false +colormapMax=NaN +colormapMin=NaN +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/CurrentCamera.properties b/devices/CurrentCamera.properties index abe48d1..daafb6c 100755 --- a/devices/CurrentCamera.properties +++ b/devices/CurrentCamera.properties @@ -1,31 +1,31 @@ -#Fri Apr 23 13:56:44 CEST 2021 -spatialCalOffsetY=-485.427166868422 -spatialCalOffsetX=-638.5965043983313 -colormapLogarithmic=false -scale=1.0 -grayscale=false -spatialCalScaleX=-35.21126791588346 -spatialCalScaleY=-48.38709170854271 -colormapMax=16542.0 -serverURL=http\://gfa-lc6-64\:8889 -rescaleOffset=0.0 -roiWidth=-1 +#Mon Dec 05 15:49:55 CET 2022 colormap=Flame -imageWidth=0 -invert=false +colormapAutomatic=true +colormapLogarithmic=false +colormapMax=255.0 colormapMin=0.0 custom=12345 -rotation=0.0 -rotationCrop=false -rescaleFactor=1.0 -imageHeight=0 -spatialCalUnits=mm -flipVertically=false -roiHeight=-1 flipHorizontally=false -colormapAutomatic=true -roiY=0 -roiX=0 -transpose=false +flipVertically=false +grayscale=false +imageHeight=0 +imageWidth=0 +invert=false regionStartX=0 regionStartY=0 +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +serverURL=http\://gfa-lc6-64\:8889 +spatialCalOffsetX=-638.5965043983313 +spatialCalOffsetY=-485.427166868422 +spatialCalScaleX=-35.21126791588346 +spatialCalScaleY=-48.38709170854271 +spatialCalUnits=mm +transpose=false diff --git a/devices/D1.properties b/devices/D1.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/D1.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/D2.properties b/devices/D2.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/D2.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/D3.properties b/devices/D3.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/D3.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/D4.properties b/devices/D4.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/D4.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/D5.properties b/devices/D5.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/D5.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/E1.properties b/devices/E1.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/E1.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/E2.properties b/devices/E2.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/E2.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/E3.properties b/devices/E3.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/E3.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/E4.properties b/devices/E4.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/E4.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/E5.properties b/devices/E5.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/E5.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/F1.properties b/devices/F1.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/F1.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/F2.properties b/devices/F2.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/F2.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/F3.properties b/devices/F3.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/F3.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/F4.properties b/devices/F4.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/F4.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/F5.properties b/devices/F5.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/F5.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/R1.properties b/devices/R1.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/R1.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/R2.properties b/devices/R2.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/R2.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/R3.properties b/devices/R3.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/R3.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/R4.properties b/devices/R4.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/R4.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/R5.properties b/devices/R5.properties new file mode 100644 index 0000000..d1b8b54 --- /dev/null +++ b/devices/R5.properties @@ -0,0 +1,3 @@ +#Thu Mar 10 13:02:31 CET 2022 +detection=Both +disabled=false diff --git a/devices/RoomTemperatureBasePlate.properties b/devices/RoomTemperatureBasePlate.properties new file mode 100644 index 0000000..c0818ed --- /dev/null +++ b/devices/RoomTemperatureBasePlate.properties @@ -0,0 +1 @@ +#Thu Mar 10 13:02:31 CET 2022 diff --git a/devices/SS_20220610_o_SS_an5_10x10_20s_0636.tif.properties b/devices/SS_20220610_o_SS_an5_10x10_20s_0636.tif.properties new file mode 100644 index 0000000..a9bc250 --- /dev/null +++ b/devices/SS_20220610_o_SS_an5_10x10_20s_0636.tif.properties @@ -0,0 +1,20 @@ +#Fri Sep 30 13:20:13 CEST 2022 +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/Time.properties b/devices/Time.properties index 2ca7570..56c0516 100755 --- a/devices/Time.properties +++ b/devices/Time.properties @@ -1,10 +1,11 @@ -#Wed Jul 25 10:47:11 CEST 2018 -maxValue=NaN +#Mon Jun 27 08:53:55 CEST 2022 minValue=NaN -offset=0.0 -precision=-1 -resolution=NaN -rotation=false -scale=1.0 -sign_bit=0 unit=null +offset=0.0 +maxValue=NaN +precision=-1 +rotation=false +sign_bit=0 +scale=1.0 +description=null +resolution=NaN diff --git a/devices/bi.properties b/devices/bi.properties new file mode 100644 index 0000000..95ca136 --- /dev/null +++ b/devices/bi.properties @@ -0,0 +1,25 @@ +#Wed Apr 20 15:41:28 CEST 2022 +spatialCalOffsetY=NaN +spatialCalOffsetX=NaN +colormapLogarithmic=false +scale=1.0 +grayscale=false +spatialCalScaleX=NaN +spatialCalScaleY=NaN +colormapMax=NaN +rescaleOffset=0.0 +roiWidth=-1 +colormap=Temperature +invert=false +colormapMin=NaN +rotation=0.0 +rotationCrop=false +rescaleFactor=1.0 +spatialCalUnits=mm +flipVertically=false +roiHeight=-1 +flipHorizontally=false +colormapAutomatic=true +roiY=0 +roiX=0 +transpose=false diff --git a/devices/bs.properties b/devices/bs.properties index f2c5ab3..c2c8702 100755 --- a/devices/bs.properties +++ b/devices/bs.properties @@ -1,7 +1,8 @@ -#Thu Nov 14 15:52:50 CET 2019 +#Tue Apr 18 14:14:16 CEST 2023 alignmentRetries=21 byteBufferAllocator=false disableCompression=true +headerReservingAllocator=false highWaterMark=100 keepListeningOnStop=false parallelHandlerProcessing=true diff --git a/devices/ca.properties b/devices/ca.properties index 821a40d..446c60e 100644 --- a/devices/ca.properties +++ b/devices/ca.properties @@ -1,4 +1,4 @@ -#Mon Mar 22 14:17:46 CET 2021 +#Fri May 07 11:51:42 CEST 2021 spatialCalOffsetY=NaN spatialCalOffsetX=NaN colormapLogarithmic=false diff --git a/devices/cam_server.properties b/devices/cam_server.properties index a654acc..4d985df 100755 --- a/devices/cam_server.properties +++ b/devices/cam_server.properties @@ -1,26 +1,26 @@ -#Thu Oct 15 09:46:07 CEST 2020 -spatialCalOffsetY=-485.427166868422 -spatialCalOffsetX=-638.5965043983313 +#Thu Oct 20 10:17:33 CEST 2022 +colormap=Flame +colormapAutomatic=true colormapLogarithmic=false -scale=1.0 -logarithmic=true +colormapMax=NaN +colormapMin=NaN +flipHorizontally=false +flipVertically=false grayscale=false +invert=false +logarithmic=true +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=-638.5965043983313 +spatialCalOffsetY=-485.427166868422 spatialCalScaleX=-35.21126791588346 spatialCalScaleY=-48.38709170854271 -colormapMax=NaN -rescaleOffset=0.0 -roiWidth=-1 -colormap=Flame -invert=false -colormapMin=NaN -rotationCrop=false -rotation=0.0 -rescaleFactor=1.0 spatialCalUnits=mm -flipVertically=false -roiHeight=-1 -flipHorizontally=false -colormapAutomatic=true -roiY=0 -roiX=0 transpose=false diff --git a/devices/chi.properties b/devices/chi.properties index 5789fcb..cfda8e1 100644 --- a/devices/chi.properties +++ b/devices/chi.properties @@ -1,16 +1,17 @@ -#Thu Aug 22 16:30:36 CEST 2019 -defaultSpeed=50.0 -estbilizationDelay=0 -maxSpeed=50.0 -maxValue=360.0 -minSpeed=0.1 -minValue=-360.0 -monitorByPosition=false +#Tue Jun 07 17:34:49 CEST 2022 offset=0.0 +maxValue=360.0 precision=2 -resolution=NaN rotation=false scale=1.0 -sign_bit=0 +description=null +estbilizationDelay=0 +maxSpeed=50.0 +resolution=NaN startRetries=1 +minValue=-360.0 unit=mm +defaultSpeed=50.0 +sign_bit=0 +monitorByPosition=false +minSpeed=0.1 diff --git a/devices/cm1.properties b/devices/cm1.properties index f461649..2ab2b3d 100644 --- a/devices/cm1.properties +++ b/devices/cm1.properties @@ -1,9 +1,10 @@ -#Tue Oct 30 09:41:22 CET 2018 -mirror_x=false -mirror_y=false +#Tue Jun 07 17:34:49 CEST 2022 precision=-1 -roi_height=-1 roi_width=-1 -roi_x=0 -roi_y=0 +mirror_y=false +mirror_x=false +description=null transpose=false +roi_y=0 +roi_height=-1 +roi_x=0 diff --git a/devices/cs.properties b/devices/cs.properties index 46b78df..0a96d1b 100755 --- a/devices/cs.properties +++ b/devices/cs.properties @@ -1,24 +1,25 @@ -#Wed Aug 09 16:33:39 CEST 2017 -colormap=Flame -colormapAutomatic=true -colormapMax=NaN -colormapMin=NaN -flipHorizontally=false -flipVertically=false -grayscale=false -invert=false -rescaleFactor=1.0 -rescaleOffset=0.0 -roiHeight=-1 -roiWidth=-1 -roiX=0 -roiY=0 -rotation=0.0 -rotationCrop=false -scale=1.0 -spatialCalOffsetX=NaN -spatialCalOffsetY=NaN -spatialCalScaleX=NaN -spatialCalScaleY=NaN -spatialCalUnits=mm -transpose=false +#Thu Oct 20 10:27:45 CEST 2022 +colormap=Flame +colormapAutomatic=true +colormapLogarithmic=false +colormapMax=NaN +colormapMin=NaN +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=-638.5965043983313 +spatialCalOffsetY=-485.427166868422 +spatialCalScaleX=-35.21126791588346 +spatialCalScaleY=-48.38709170854271 +spatialCalUnits=mm +transpose=false diff --git a/devices/curve_fitting.png.properties b/devices/curve_fitting.png.properties new file mode 100644 index 0000000..a801b6c --- /dev/null +++ b/devices/curve_fitting.png.properties @@ -0,0 +1,20 @@ +#Fri Jun 10 09:17:40 CEST 2022 +spatialCalOffsetY=NaN +invert=false +spatialCalOffsetX=NaN +rotation=0.0 +rotationCrop=false +scale=1.0 +rescaleFactor=1.0 +grayscale=false +spatialCalUnits=mm +flipVertically=false +roiHeight=-1 +spatialCalScaleX=NaN +spatialCalScaleY=NaN +flipHorizontally=false +roiY=0 +roiX=0 +rescaleOffset=0.0 +transpose=false +roiWidth=-1 diff --git a/devices/cv.properties b/devices/cv.properties index 3d57b9d..42eda39 100755 --- a/devices/cv.properties +++ b/devices/cv.properties @@ -1,10 +1,11 @@ -#Tue Jun 19 17:19:36 CEST 2018 +#Wed Jun 08 14:29:43 CEST 2022 accessType=ReadWrite -maxValue=1000.0 minValue=-1000.0 +unit=mm offset=0.0 -precision=3 -resolution=1.0 -scale=1.0 +maxValue=1000.0 +precision=5 sign_bit=0 -unit=C +scale=1.0 +description=Test Calcout Inopput +resolution=1.0 diff --git a/devices/delta.properties b/devices/delta.properties index 7adb232..f5f59da 100644 --- a/devices/delta.properties +++ b/devices/delta.properties @@ -1,16 +1,17 @@ -#Thu Aug 22 16:30:36 CEST 2019 -defaultSpeed=50.0 -estbilizationDelay=0 -maxSpeed=50.0 -maxValue=190.0 -minSpeed=0.1 -minValue=-19.0 -monitorByPosition=false +#Tue Jun 07 17:34:49 CEST 2022 offset=0.0 +maxValue=190.0 precision=2 -resolution=NaN rotation=false scale=1.0 -sign_bit=0 +description=null +estbilizationDelay=0 +maxSpeed=50.0 +resolution=NaN startRetries=1 +minValue=-19.0 unit=mm +defaultSpeed=50.0 +sign_bit=0 +monitorByPosition=false +minSpeed=0.1 diff --git a/devices/dispatcher.properties b/devices/dispatcher.properties index 6a28723..3b85b77 100755 --- a/devices/dispatcher.properties +++ b/devices/dispatcher.properties @@ -1,8 +1,9 @@ -#Thu Nov 14 15:57:07 CET 2019 +#Tue Apr 18 14:14:16 CEST 2023 alignmentRetries=20 byteBufferAllocator=false disableCompression=true dropIncomplete=true +headerReservingAllocator=false keepListeningOnStop=true mappingIncomplete=fill_null parallelHandlerProcessing=false diff --git a/devices/dp1.properties b/devices/dp1.properties index 6719cb3..c86dd93 100755 --- a/devices/dp1.properties +++ b/devices/dp1.properties @@ -1,11 +1,12 @@ -#Tue Jan 17 10:47:10 CET 2017 -motor1=0.0|4.0|8.0|0.0 -motor2=0.0|5.0|3.0|NaN -motor3=null -motor4=null -motor5=null -motor6=null -motor7=null -motor8=null -positions=Park|Ready|Out|Clear -precision=-1 +#Wed Jun 15 15:14:06 CEST 2022 +motor7=null +motor8=null +motor5=null +motor6=null +precision=-1 +description=null +positions=Park|Ready|Out|Clear +motor3=null +motor4=null +motor1=0.0|4.0|8.0|0.0 +motor2=0.0|5.0|3.0|NaN diff --git a/devices/eiger.properties b/devices/eiger.properties new file mode 100644 index 0000000..1933f9c --- /dev/null +++ b/devices/eiger.properties @@ -0,0 +1,17 @@ +#Wed Oct 05 10:33:50 CEST 2022 +defaultSpeed=1.0 +description=null +estbilizationDelay=0 +maxSpeed=10.0 +maxValue=10.0 +minSpeed=0.1 +minValue=-10.0 +monitorByPosition=false +offset=0.0 +precision=2 +resolution=NaN +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/energy.properties b/devices/energy.properties index f443fec..cea9e32 100644 --- a/devices/energy.properties +++ b/devices/energy.properties @@ -1,10 +1,17 @@ -#Fri Aug 24 11:58:39 CEST 2018 -maxValue=NaN -minValue=NaN +#Tue Jun 07 17:34:49 CEST 2022 offset=0.0 +maxValue=200.0 precision=-1 -resolution=NaN rotation=false scale=1.0 -sign_bit=0 +description=null +estbilizationDelay=0 +maxSpeed=50.0 +resolution=NaN +startRetries=1 +minValue=-200.0 unit=null +defaultSpeed=50.0 +sign_bit=0 +monitorByPosition=false +minSpeed=0.1 diff --git a/devices/eta.properties b/devices/eta.properties index 8dcfd70..b091203 100644 --- a/devices/eta.properties +++ b/devices/eta.properties @@ -1,5 +1,6 @@ -#Thu Aug 22 16:30:36 CEST 2019 +#Fri Mar 10 11:21:22 CET 2023 defaultSpeed=50.0 +description=null estbilizationDelay=0 maxSpeed=50.0 maxValue=189.0 diff --git a/devices/gamma.properties b/devices/gamma.properties index e540ac5..5b8d424 100644 --- a/devices/gamma.properties +++ b/devices/gamma.properties @@ -1,16 +1,17 @@ -#Thu Aug 22 16:30:36 CEST 2019 -defaultSpeed=50.0 -estbilizationDelay=0 -maxSpeed=50.0 -maxValue=164.0 -minSpeed=0.1 -minValue=-61.0 -monitorByPosition=false +#Tue Jun 07 17:34:49 CEST 2022 offset=0.0 +maxValue=164.0 precision=2 -resolution=NaN rotation=false scale=1.0 -sign_bit=0 +description=null +estbilizationDelay=0 +maxSpeed=50.0 +resolution=NaN startRetries=1 +minValue=-61.0 unit=mm +defaultSpeed=50.0 +sign_bit=0 +monitorByPosition=false +minSpeed=0.1 diff --git a/devices/histo.properties b/devices/histo.properties index ff1e6b8..d333a86 100644 --- a/devices/histo.properties +++ b/devices/histo.properties @@ -1,7 +1,8 @@ -#Wed Nov 27 17:20:46 CET 2019 +#Tue Jun 07 17:34:49 CEST 2022 bins=100 -interval=0 -max=150.0 min=80.0 -numberOfSamples=1000 +max=150.0 precision=-1 +description=null +interval=0 +numberOfSamples=1000 diff --git a/devices/image.properties b/devices/image.properties index 88471a5..90114e9 100644 --- a/devices/image.properties +++ b/devices/image.properties @@ -1,25 +1,25 @@ -#Wed Oct 30 11:35:11 CET 2019 -spatialCalOffsetY=NaN -spatialCalOffsetX=NaN +#Thu Nov 10 09:48:10 CET 2022 +colormap=Viridis +colormapAutomatic=true colormapLogarithmic=false -scale=1.0 -grayscale=false -spatialCalScaleX=NaN -spatialCalScaleY=NaN colormapMax=NaN -rescaleOffset=0.0 -roiWidth=-1 -colormap=Grayscale -invert=false colormapMin=NaN +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 rotation=0.0 rotationCrop=false -rescaleFactor=1.0 +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN spatialCalUnits=mm -flipVertically=false -roiHeight=-1 -flipHorizontally=false -colormapAutomatic=false -roiY=0 -roiX=0 transpose=false diff --git a/devices/m1.properties b/devices/m1.properties index e52ab5f..4faeae6 100755 --- a/devices/m1.properties +++ b/devices/m1.properties @@ -1,18 +1,19 @@ -#Thu Nov 07 10:43:19 CET 2019 -defaultSpeed=1.0 -estbilizationDelay=0 -hasEnable=false -homingType=null -maxSpeed=10.0 -maxValue=10.0 -minSpeed=0.1 -minValue=-10.0 -monitorByPosition=false +#Wed Jun 15 15:14:06 CEST 2022 offset=0.0 +maxValue=10.0 precision=2 -resolution=NaN rotation=false scale=1.0 -sign_bit=0 +description=null +estbilizationDelay=0 +maxSpeed=10.0 +resolution=NaN +homingType=null startRetries=1 +minValue=-10.0 unit=mm +defaultSpeed=1.0 +hasEnable=false +sign_bit=0 +monitorByPosition=false +minSpeed=0.1 diff --git a/devices/m2.properties b/devices/m2.properties index 5480605..f6ea5a0 100755 --- a/devices/m2.properties +++ b/devices/m2.properties @@ -1,9 +1,10 @@ -#Tue Oct 29 14:15:33 CET 2019 +#Wed Jun 15 15:14:06 CEST 2022 offset=0.0 maxValue=10.0 precision=4 rotation=false scale=1.0 +description=null estbilizationDelay=0 maxSpeed=10.0 resolution=NaN diff --git a/devices/m3.properties b/devices/m3.properties new file mode 100644 index 0000000..4572867 --- /dev/null +++ b/devices/m3.properties @@ -0,0 +1,17 @@ +#Mon Oct 03 14:50:52 CEST 2022 +defaultSpeed=1.0 +description=null +estbilizationDelay=0 +maxSpeed=10.0 +maxValue=10.0 +minSpeed=0.1 +minValue=-10.0 +monitorByPosition=false +offset=0.0 +precision=2 +resolution=NaN +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/m4.properties b/devices/m4.properties new file mode 100644 index 0000000..155d291 --- /dev/null +++ b/devices/m4.properties @@ -0,0 +1,17 @@ +#Mon Oct 03 14:53:36 CEST 2022 +defaultSpeed=1.0 +description=null +estbilizationDelay=0 +maxSpeed=10.0 +maxValue=10.0 +minSpeed=0.1 +minValue=-10.0 +monitorByPosition=false +offset=0.0 +precision=2 +resolution=NaN +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/m5.properties b/devices/m5.properties new file mode 100644 index 0000000..607a382 --- /dev/null +++ b/devices/m5.properties @@ -0,0 +1,17 @@ +#Mon Oct 03 14:54:33 CEST 2022 +defaultSpeed=1.0 +description=null +estbilizationDelay=0 +maxSpeed=10.0 +maxValue=10.0 +minSpeed=0.1 +minValue=-10.0 +monitorByPosition=false +offset=0.0 +precision=2 +resolution=NaN +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/m6.properties b/devices/m6.properties new file mode 100644 index 0000000..4af44f6 --- /dev/null +++ b/devices/m6.properties @@ -0,0 +1,17 @@ +#Mon Oct 03 17:17:36 CEST 2022 +defaultSpeed=1.0 +description=null +estbilizationDelay=0 +maxSpeed=10.0 +maxValue=10.0 +minSpeed=0.1 +minValue=-10.0 +monitorByPosition=false +offset=0.0 +precision=2 +resolution=NaN +rotation=false +scale=1.0 +sign_bit=0 +startRetries=1 +unit=mm diff --git a/devices/manip.properties b/devices/manip.properties index 195d6c5..b4c907b 100755 --- a/devices/manip.properties +++ b/devices/manip.properties @@ -1,8 +1,9 @@ -#Tue Mar 17 16:26:08 CET 2015 -accessType=ReadWrite -position_pvs=TESTIOC-MA\:RETRACTED|TESTIOC-MA\:YAG |TESTIOC-MA\:NE |TESTIOC-MA\:TRCL|TESTIOC-MA\:SHIELD|TESTIOC-MA\:CLAMP|TESTIOC-MA\:HEATER|TESTIOC-MA\:SAMPLE -positions=Retracted |YAG |Normal emission|Transfer \t | Shield |Clamping screw |Heater screw |Sample access -positions_pvs=asd|zxc -precision=-1 -readback_pv=TESTIOC-MA\:STS -stop_pv=TESTIOC-MA-STOP\:ALL +#Tue Jun 07 17:34:49 CEST 2022 +accessType=ReadWrite +positions_pvs=asd|zxc +precision=-1 +description=null +positions=Retracted |YAG |Normal emission|Transfer \t | Shield |Clamping screw |Heater screw |Sample access +readback_pv=TESTIOC-MA\:STS +position_pvs=TESTIOC-MA\:RETRACTED|TESTIOC-MA\:YAG |TESTIOC-MA\:NE |TESTIOC-MA\:TRCL|TESTIOC-MA\:SHIELD|TESTIOC-MA\:CLAMP|TESTIOC-MA\:HEATER|TESTIOC-MA\:SAMPLE +stop_pv=TESTIOC-MA-STOP\:ALL diff --git a/devices/maser.properties b/devices/maser.properties new file mode 100644 index 0000000..d367a39 --- /dev/null +++ b/devices/maser.properties @@ -0,0 +1,10 @@ +#Thu Sep 09 14:40:03 CEST 2021 +minValue=NaN +unit=null +offset=0.0 +maxValue=NaN +rotation=false +precision=-1 +sign_bit=0 +scale=1.0 +resolution=NaN diff --git a/devices/master.properties b/devices/master.properties index 42f7e5f..54bac11 100755 --- a/devices/master.properties +++ b/devices/master.properties @@ -1,8 +1,26 @@ -#Thu Dec 24 09:57:56 CET 2015 -offsetReadAnalogInput=0 -offsetReadAnalogOutput=0 -offsetReadDigitalInput=0 -offsetReadDigitalOutput=0 -offsetWriteAnalogOutput=0 -offsetWriteDigitalOutput=0 -timeout=1000 +#Tue Jun 07 17:34:49 CEST 2022 +slave2Positions=-2.0|0.22|2.0 +offsetReadDigitalOutput=0 +slave5Positions=null +precision=2 +scale=1.0 +description=null +resolution=NaN +timeout=1000 +offsetWriteAnalogOutput=0 +mode=LINEAR +minValue=-13.0 +sign_bit=0 +masterPositions=-1.0|0.0|2.0 +offsetWriteDigitalOutput=0 +offsetReadDigitalInput=0 +offset=0.0 +maxValue=116.0 +rotation=false +slave4Positions=null +offsetReadAnalogOutput=0 +unit=mm +offsetReadAnalogInput=0 +slave1Positions=-1.5|0.02|1.5 +slave3Positions=-2.5|0.4|2.5 +slave6Positions=null diff --git a/devices/motor.properties b/devices/motor.properties index 8e52640..05df8ca 100755 --- a/devices/motor.properties +++ b/devices/motor.properties @@ -1,18 +1,19 @@ -#Thu Aug 22 16:30:36 CEST 2019 -defaultSpeed=1.0 -estbilizationDelay=0 -hasEnable=true -homingType=Backward -maxSpeed=20.0 -maxValue=75.0 -minSpeed=0.001 -minValue=-5.0 -monitorByPosition=false +#Tue Jun 07 17:34:49 CEST 2022 offset=0.0 +maxValue=100.0 precision=4 -resolution=0.00125 rotation=false scale=1.0 -sign_bit=0 +description=null +estbilizationDelay=0 +maxSpeed=20.0 +resolution=0.00125 +homingType=Backward startRetries=2 +minValue=-100.0 unit=mm +defaultSpeed=1.0 +sign_bit=0 +hasEnable=true +monitorByPosition=false +minSpeed=0.001 diff --git a/devices/motor2.properties b/devices/motor2.properties index 197cf8a..856cebe 100755 --- a/devices/motor2.properties +++ b/devices/motor2.properties @@ -1,22 +1,23 @@ -#Thu Aug 22 16:30:36 CEST 2019 -accessType=ReadWrite -channel=MTEST-GOBBO\:MOT2 -defaultSpeed=0.2 -estbilizationDelay=0 -hasEnable=true -homingDirection=Backward -homingType=Backward -maxSpeed=100.0 -maxValue=100.0 -minSpeed=0.001 -minValue=-100.0 -monitorByPosition=false +#Tue Jun 07 17:34:49 CEST 2022 offset=10.0 -precision=3 -resolution=NaN -rotation=false -scale=2.0 -sign_bit=0 +maxValue=100.0 simulation=false +precision=3 +rotation=false +channel=MTEST-GOBBO\:MOT2 +scale=2.0 +description=null +estbilizationDelay=0 +maxSpeed=100.0 +resolution=NaN +homingType=Backward +accessType=ReadWrite startRetries=1 +minValue=-100.0 unit=null +defaultSpeed=0.2 +homingDirection=Backward +hasEnable=true +sign_bit=0 +monitorByPosition=false +minSpeed=0.001 diff --git a/devices/mt.properties b/devices/mt.properties index 96d7bad..2ab2b3d 100644 --- a/devices/mt.properties +++ b/devices/mt.properties @@ -1,9 +1,10 @@ -#Tue Oct 30 08:49:48 CET 2018 -mirror_x=false -mirror_y=false +#Tue Jun 07 17:34:49 CEST 2022 precision=-1 -roi_height=-1 roi_width=-1 -roi_x=0 -roi_y=0 +mirror_y=false +mirror_x=false +description=null transpose=false +roi_y=0 +roi_height=-1 +roi_x=0 diff --git a/devices/mt1.properties b/devices/mt1.properties index 96d7bad..2ab2b3d 100644 --- a/devices/mt1.properties +++ b/devices/mt1.properties @@ -1,9 +1,10 @@ -#Tue Oct 30 08:49:48 CET 2018 -mirror_x=false -mirror_y=false +#Tue Jun 07 17:34:49 CEST 2022 precision=-1 -roi_height=-1 roi_width=-1 -roi_x=0 -roi_y=0 +mirror_y=false +mirror_x=false +description=null transpose=false +roi_y=0 +roi_height=-1 +roi_x=0 diff --git a/devices/mu.properties b/devices/mu.properties index fe59d0d..4e5f45c 100644 --- a/devices/mu.properties +++ b/devices/mu.properties @@ -1,16 +1,17 @@ -#Thu Aug 22 16:30:36 CEST 2019 -defaultSpeed=0.2 -estbilizationDelay=0 -maxSpeed=50.0 -maxValue=116.0 -minSpeed=0.1 -minValue=-13.0 -monitorByPosition=false +#Tue Jun 07 17:34:49 CEST 2022 offset=0.0 +maxValue=116.0 precision=2 -resolution=NaN rotation=false scale=1.0 -sign_bit=0 +description=null +estbilizationDelay=0 +maxSpeed=50.0 +resolution=NaN startRetries=1 +minValue=-13.0 unit=mm +defaultSpeed=0.2 +sign_bit=0 +monitorByPosition=false +minSpeed=0.1 diff --git a/devices/p1.properties b/devices/p1.properties index ab9746b..21d89dc 100755 --- a/devices/p1.properties +++ b/devices/p1.properties @@ -1,10 +1,11 @@ -#Mon Aug 06 08:57:31 CEST 2018 -maxValue=NaN +#Wed Jun 15 15:14:06 CEST 2022 minValue=NaN -offset=0.0 -precision=-1 -resolution=NaN -rotation=false -scale=1.0 -sign_bit=0 unit=null +offset=0.0 +maxValue=NaN +precision=-1 +rotation=false +sign_bit=0 +scale=1.0 +description=null +resolution=NaN diff --git a/devices/pe.properties b/devices/pe.properties index d7f9bfd..5f4eef2 100755 --- a/devices/pe.properties +++ b/devices/pe.properties @@ -1,11 +1,12 @@ -#Tue Jun 19 17:19:36 CEST 2018 +#Tue Jun 07 17:34:49 CEST 2022 accessType=ReadWrite -maxValue=1000.0 -minValue=0.0 -offset=0.0 -precision=5 -resolution=NaN -rotation=true -scale=1.0 -sign_bit=0 +minValue=-1000.0 unit=mm +offset=0.0 +maxValue=1000.0 +precision=5 +rotation=true +sign_bit=0 +scale=1.0 +description=null +resolution=NaN diff --git a/devices/phi.properties b/devices/phi.properties index 5789fcb..cfda8e1 100644 --- a/devices/phi.properties +++ b/devices/phi.properties @@ -1,16 +1,17 @@ -#Thu Aug 22 16:30:36 CEST 2019 -defaultSpeed=50.0 -estbilizationDelay=0 -maxSpeed=50.0 -maxValue=360.0 -minSpeed=0.1 -minValue=-360.0 -monitorByPosition=false +#Tue Jun 07 17:34:49 CEST 2022 offset=0.0 +maxValue=360.0 precision=2 -resolution=NaN rotation=false scale=1.0 -sign_bit=0 +description=null +estbilizationDelay=0 +maxSpeed=50.0 +resolution=NaN startRetries=1 +minValue=-360.0 unit=mm +defaultSpeed=50.0 +sign_bit=0 +monitorByPosition=false +minSpeed=0.1 diff --git a/devices/pkh1mot.properties b/devices/pkh1mot.properties new file mode 100644 index 0000000..270ae9c --- /dev/null +++ b/devices/pkh1mot.properties @@ -0,0 +1,16 @@ +#Thu Jul 01 09:27:29 CEST 2021 +offset=0.0 +maxValue=10.0 +rotation=false +precision=2 +scale=1.0 +estbilizationDelay=0 +maxSpeed=10.0 +resolution=NaN +startRetries=1 +minValue=-10.0 +unit=mm +defaultSpeed=1.0 +sign_bit=0 +monitorByPosition=false +minSpeed=0.1 diff --git a/devices/proc_data.properties b/devices/proc_data.properties new file mode 100644 index 0000000..b370f9a --- /dev/null +++ b/devices/proc_data.properties @@ -0,0 +1,25 @@ +#Thu Nov 10 13:19:15 CET 2022 +colormap=Flame +colormapAutomatic=false +colormapLogarithmic=false +colormapMax=NaN +colormapMin=NaN +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/proc_image.properties b/devices/proc_image.properties new file mode 100644 index 0000000..f1089d5 --- /dev/null +++ b/devices/proc_image.properties @@ -0,0 +1,25 @@ +#Thu Nov 10 10:04:38 CET 2022 +colormap=Viridis +colormapAutomatic=true +colormapLogarithmic=false +colormapMax=NaN +colormapMin=NaN +flipHorizontally=false +flipVertically=false +grayscale=false +invert=false +rescaleFactor=1.0 +rescaleOffset=0.0 +roiHeight=-1 +roiWidth=-1 +roiX=0 +roiY=0 +rotation=0.0 +rotationCrop=false +scale=1.0 +spatialCalOffsetX=NaN +spatialCalOffsetY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +spatialCalUnits=mm +transpose=false diff --git a/devices/pv.properties b/devices/pv.properties index 22025fe..651b544 100755 --- a/devices/pv.properties +++ b/devices/pv.properties @@ -1,12 +1,13 @@ -#Tue Jun 19 17:19:36 CEST 2018 -accessType=ReadWrite -channel=TESTIOC\:TESTCALCOUT\:Input -maxValue=180.0 -minValue=-180.0 +#Wed Jun 08 14:15:08 CEST 2022 offset=1.0 -precision=5 -resolution=-1.0 -scale=2.0 -sign_bit=0 +maxValue=1000.0 simulation=false +precision=5 +channel=TESTIOC\:TESTCALCOUT\:Input +scale=2.0 +description=Test Calcout Inopput +resolution=-1.0 +accessType=ReadWrite +minValue=-1000.0 unit=mm +sign_bit=0 diff --git a/devices/sc.properties b/devices/sc.properties index 2f33b5d..fd6e9f3 100755 --- a/devices/sc.properties +++ b/devices/sc.properties @@ -1,33 +1,34 @@ -#Wed Sep 21 16:35:18 CEST 2016 -binning=1 -calOffsetX=NaN -calOffsetY=NaN -calScaleX=NaN -calScaleY=NaN -colormap=Grayscale -colormapAutomatic=true -colormapMax=NaN -colormapMin=NaN -dataMonitoring=true -dataPolling=1000 -flipHorizontally=true -flipVertically=true -grayscale=false -invert=false -pollingBackground=false -pollingInterval=0 -rescaleFactor=1.0 -rescaleOffset=0.0 -roiHeight=-1 -roiWidth=-1 -roiX=0 -roiY=0 -rotation=0.0 -rotationCrop=false -scale=1.0 -spatialCalOffsetX=NaN -spatialCalOffsetY=NaN -spatialCalScaleX=NaN -spatialCalScaleY=NaN -spatialCalUnits=mm -transpose=false +#Wed Sep 22 11:54:38 CEST 2021 +spatialCalOffsetY=NaN +spatialCalOffsetX=NaN +dataPolling=1000 +pollingInterval=0 +colormapLogarithmic=false +scale=1.0 +grayscale=false +calScaleX=NaN +calScaleY=NaN +spatialCalScaleX=NaN +spatialCalScaleY=NaN +colormapMax=NaN +rescaleOffset=0.0 +roiWidth=-1 +colormap=Grayscale +pollingBackground=false +invert=false +colormapMin=NaN +rotation=0.0 +rotationCrop=false +binning=1 +rescaleFactor=1.0 +spatialCalUnits=mm +flipVertically=true +roiHeight=-1 +calOffsetX=NaN +flipHorizontally=true +colormapAutomatic=true +dataMonitoring=true +roiY=0 +calOffsetY=NaN +roiX=0 +transpose=false diff --git a/devices/tab.properties b/devices/tab.properties index 4065134..baec9ab 100755 --- a/devices/tab.properties +++ b/devices/tab.properties @@ -1,14 +1,15 @@ -#Thu Dec 17 09:44:30 CET 2015 -accessType=ReadWrite -motor1=0.0 | 1.0 | 2.0 | 0.1 -motor10=null -motor2=0.0 | 1.0 |2.0 | NaN -motor3= -motor4= -motor5= -motor6= -motor7= -motor8= -motor9=null -positions=Park | Ready | Out | Clear -precision=-1 +#Tue Jun 07 17:34:49 CEST 2022 +precision=-1 +description=null +positions=Park | Ready | Out | Clear +motor3= +motor4= +motor1=0.0 | 1.0 | 2.0 | 0.1 +motor2=0.0 | 1.0 |2.0 | NaN +accessType=ReadWrite +motor7= +motor8= +motor5= +motor10=null +motor6= +motor9=null diff --git a/plugins/.DS_Store b/plugins/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..db29d4e9ab6b00cff78701f66780aa0275e13dd3 GIT binary patch literal 16388 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 m) { + peak[1] = peak[1] - mlarge; + } + if (peak[2] > n) { + peak[2] = peak[2] - nlarge; + } + + //% If upsampling > 2, then refine estimate with matrix multiply DFT + if (this.usfac > 2) { + // %%% DFT computation %%% + // % Initial shift estimate in upsampled grid + double row_shift = Math.round(peak[1]/2.0*this.usfac)/this.usfac; + double col_shift = Math.round(peak[2]/2.0*this.usfac)/this.usfac; + int dftshift = (int)Math.floor(Math.ceil(this.usfac*1.5)/2); // Center of output array at dftshift+1 + // % Matrix multiply DFT around the current shift estimate + ComplexMatrix in = ElementProduct(drifted, ref.conjugate()); + ComplexMatrix nCC = dftups(in, (int)Math.ceil(this.usfac*1.5), (int)Math.ceil(this.usfac*1.5), + dftshift-row_shift*this.usfac, dftshift-col_shift*this.usfac); + nCC = nCC.times(1.0/(m*n*this.usfac*this.usfac)).conjugate(); + // % Locate maximum and map back to original pixel grid + double[] npeak = cFindPeak(nCC); //max_r, max_i, r, c + + ComplexMatrix mrg00 = dftups(ElementProduct(ref, ref.conjugate()),1,1,0,0); + double rg00 = mrg00.getElementReference(0, 0).abs()/(m*n*this.usfac*this.usfac); + ComplexMatrix mrf00 = dftups(ElementProduct(drifted, drifted.conjugate()),1,1,0,0); + double rf00 = mrf00.getElementReference(0, 0).abs()/(m*n*this.usfac*this.usfac); + + npeak[1] = npeak[1] - dftshift; + npeak[2] = npeak[2] - dftshift; + output[0] = Math.sqrt(Math.abs(1.0 - npeak[0]*npeak[0]/(rg00*rf00))); //error + output[1] = Math.atan2(npeak[4], npeak[3]); //diffphase + output[2] = row_shift + npeak[1]/this.usfac; //delta row + output[3] = col_shift + npeak[2]/this.usfac; //delta col + + } else { + // % If upsampling = 2, no additional pixel shift refinement + double rg00 = SumSquareAbs(ref)/(mlarge*nlarge); + double rf00 = SumSquareAbs(drifted)/(mlarge*nlarge); + + output[0] = Math.sqrt(Math.abs(1.0 - peak[0]*peak[0]/(rg00*rf00))); //error + output[1] = Math.atan2(peak[4], peak[3]); //diffphase + output[2] = peak[1]/2.0; //delta row + output[3] = peak[2]/2.0; //delta col + } + + return output; + } + + private double SumSquareAbs(ComplexMatrix m) { + double sum = 0.0; + + for (int j = 0; j < m.getNrow(); j ++){ + for (int i = 0; i < m.getNcol(); i++) { + sum += m.getElementReference(j, i).squareAbs(); + } + } + + return sum; + } + + private double[] cFindPeak(ComplexMatrix m) { + double max = 0.0; + double realmax = 0.0; + double imagmax = 0.0; + int cmax = 0, rmax = 0; + + for (int j = 0; j < m.getNrow(); j ++){ + for (int i = 0; i < m.getNcol(); i++) { + if (m.getElementReference(j, i).abs() > max) { + max = m.getElementReference(j, i).abs(); + realmax = m.getElementReference(j, i).getReal(); + imagmax = m.getElementReference(j, i).getImag(); + rmax = j; + cmax = i; + } + } + } + + double[] res = new double[5]; + res[0] = Math.sqrt(realmax*realmax+imagmax*imagmax); res[1] = rmax; res[2] = cmax; + res[3] = realmax; res[4] = imagmax; + return res; + } + + private ComplexMatrix fftshift(ComplexMatrix in) { + int nc = in.getNcol(); + int nr = in.getNrow(); + + ComplexMatrix out = new ComplexMatrix (nr, nc); + + int midi = (int)Math.floor(nc/2.0); + int offi = (int)Math.ceil(nc/2.0); + int midj = (int)Math.floor(nr/2.0); + int offj = (int)Math.ceil(nr/2.0); + + for (int j = 0; j < nr; j ++){ + for (int i = 0; i < nc; i++) { + if (j < midj) { + if (i < midi) { + out.setElement(j, i, in.getElementReference(j+offj, i+offi)); + } else { + out.setElement(j, i, in.getElementReference(j+offj, i-midi)); + } + } else { + if (i < midi) { + out.setElement(j, i, in.getElementReference(j-midj, i+offi)); + } else { + out.setElement(j, i, in.getElementReference(j-midj, i-midi)); + } + } + } + } + + return out; + } + + private ComplexMatrix ifftshift(ComplexMatrix in) { + int nc = in.getNcol(); + int nr = in.getNrow(); + + ComplexMatrix out = new ComplexMatrix (nr, nc); + + int midi = (int)Math.ceil(nc/2.0); + int offi = (int)Math.floor(nc/2.0); + int midj = (int)Math.ceil(nr/2.0); + int offj = (int)Math.floor(nr/2.0); + + for (int j = 0; j < nr; j ++){ + for (int i = 0; i < nc; i++) { + if (j < midj) { + if (i < midi) { + out.setElement(j, i, in.getElementReference(j+offj, i+offi)); + } else { + out.setElement(j, i, in.getElementReference(j+offj, i-midi)); + } + } else { + if (i < midi) { + out.setElement(j, i, in.getElementReference(j-midj, i+offi)); + } else { + out.setElement(j, i, in.getElementReference(j-midj, i-midi)); + } + } + } + } + + return out; + } + + private Matrix ifftshift(Matrix in) { + int nc = in.getNcol(); + int nr = in.getNrow(); + + Matrix out = new Matrix (nr, nc); + + int midi = (int)Math.ceil(nc/2.0); + int offi = (int)Math.floor(nc/2.0); + int midj = (int)Math.ceil(nr/2.0); + int offj = (int)Math.floor(nr/2.0); + + for (int j = 0; j < nr; j ++){ + for (int i = 0; i < nc; i++) { + if (j < midj) { + if (i < midi) { + out.setElement(j, i, in.getElement(j+offj, i+offi)); + } else { + out.setElement(j, i, in.getElement(j+offj, i-midi)); + } + } else { + if (i < midi) { + out.setElement(j, i, in.getElement(j-midj, i+offi)); + } else { + out.setElement(j, i, in.getElement(j-midj, i-midi)); + } + } + } + } + + return out; + } + + private ComplexMatrix dftups(ComplexMatrix in, int nor, int noc, double roff, double coff) { + // function out=dftups(in,nor,noc,usfac,roff,coff); + // Upsampled DFT by matrix multiplies, can compute an upsampled DFT in just + // a small region. + // usfac Upsampling factor (default usfac = 1) + // [nor,noc] Number of pixels in the output upsampled DFT, in + // units of upsampled pixels (default = size(in)) + // roff, coff Row and column offsets, allow to shift the output array to + // a region of interest on the DFT (default = 0) + // Recieves DC in upper left corner, image center must be in (1,1) + // Loïc Le Guyader - Jun 11, 2011 Java version for ImageJ plugin + // Manuel Guizar - Dec 13, 2007 + // Modified from dftus, by J.R. Fienup 7/31/06 + + // This code is intended to provide the same result as if the following + // operations were performed + // - Embed the array "in" in an array that is usfac times larger in each + // dimension. ifftshift to bring the center of the image to (1,1). + // - Take the FFT of the larger array + // - Extract an [nor, noc] region of the result. Starting with the + // [roff+1 coff+1] element. + + // It achieves this result by computing the DFT in the output array without + // the need to zeropad. Much faster and memory efficient than the + // zero-padded FFT approach if [nor noc] are much smaller than [nr*usfac nc*usfac] + + + int nr = in.getNrow(); + int nc = in.getNcol(); + + // Compute kernels and obtain DFT by matrix products + double amplitude = -2.0*Math.PI/(nc*usfac); + + Matrix u = new Matrix(nc, 1); + for (int i = 0; i < nc; i++) { + u.setElement(i, 0, i - Math.floor(nc/2.0)); + } + u = ifftshift(u); + + Matrix v = new Matrix(1, noc); + for (int i = 0; i < noc; i++) { + v.setElement(0, i, i-coff); + } + + Matrix phase = u.times(v); + ComplexMatrix kernc = new ComplexMatrix(nc, noc); + for (int j = 0; j < nc; j++) { + for (int i = 0; i < noc; i++) { + Complex t = new Complex(); + t.polar(1.0, amplitude*phase.getElement(j, i)); + kernc.setElement(j, i, t); + } + } + //ComplexMatrixPrint(kernc); + + amplitude = -2.0*Math.PI/(nr*usfac); + + Matrix w = new Matrix(nor, 1); + for (int i = 0; i < nor; i++) { + w.setElement(i, 0, i - roff); + } + + Matrix x = new Matrix(1, nr); + for (int i = 0; i < nr; i++) { + x.setElement(0, i, i - Math.floor(nr/2.0)); + } + x = ifftshift(x); + + Matrix nphase = w.times(x); + ComplexMatrix kernr = new ComplexMatrix(nor, nr); + for (int j = 0; j < nor; j++) { + for (int i = 0; i < nr; i++) { + Complex t = new Complex(); + t.polar(1.0, amplitude*nphase.getElement(j, i)); + kernr.setElement(j, i, t); + } + } + //ComplexMatrixPrint(kernr); + + ComplexMatrix out = kernr.times(in.times(kernc)); + + return out; + } + + private double[][] CrossCorrelation(ComplexMatrix ref, ComplexMatrix drifted) { + int h = ref.getNrow(); + int w = ref.getNcol(); + ComplexMatrix b = drifted.conjugate(); + ComplexMatrix res = ElementProduct(ref, b); + + double[][] data = ComplexMatrix_to_FFTArray2D(res); + DoubleFFT_2D fft = new DoubleFFT_2D(h, w); + fft.realInverse(data, true); + + return data; + } + + private ComplexMatrix ElementProduct(ComplexMatrix a, ComplexMatrix b) { + int nr = a.getNrow(); + int nc = a.getNcol(); + + ComplexMatrix res = new ComplexMatrix(nr, nc); + + for(int j = 0; j < nr; j++) { + for(int i = 0; i < nc; i++) { + res.setElement(j, i, a.getElementReference(j, i).times(b.getElementReference(j, i))); + } + } + + return res; + } + + private double[][] ImageProcessor_to_FFTArray2D(ImageProcessor ip) { + + float[] pixels = (float[])ip.getPixels(); + int w = ip.getWidth(); + int h = ip.getHeight(); + double[][] data = new double[h][w]; + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + data[j][i] = (double)pixels[j*w + i]; + } + } + + return data; + } + + private double[][] ImageProcessor_to_FFTComplexArray2D(ImageProcessor ip_r, ImageProcessor ip_i) { + + float[] pixels_r = (float[])ip_r.getPixels(); + float[] pixels_i = (float[])ip_i.getPixels(); + int w = ip_r.getWidth(); + int h = ip_r.getHeight(); + double[][] data = new double[h][2*w]; + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + data[j][2*i] = (double)pixels_r[j*w + i]; + data[j][2*i+1] = (double)pixels_i[j*w + i]; + } + } + + return data; + } + + private ComplexMatrix FFTArray2D_to_ComplexMatrix(double[][] data, int h, int w) { + + ComplexMatrix m = new ComplexMatrix(h,w); + + for (int j = 0; j < h; j++) { + for (int i = 0; i <= w/2; i++) { + if (j > 0 && i > 0 && i < w/2) { + m.setElement(j,i, new Complex(data[j][2*i], data[j][2*i+1])); + m.setElement(h-j, w-i, new Complex(data[j][2*i], -data[j][2*i+1])); + } + if (j == 0 && i > 0 && i < w/2) { + m.setElement(0, i, new Complex(data[0][2*i], data[0][2*i+1])); + m.setElement(0, w-i, new Complex(data[0][2*i], -data[0][2*i+1])); + } + if (i == 0 && j > 0 && j < h/2) { + m.setElement(j,0, new Complex(data[j][0], data[j][1])); + m.setElement(h-j, 0, new Complex(data[j][0], -data[j][1])); + m.setElement(j, w/2, new Complex(data[h-j][1], -data[h-j][0])); + m.setElement(h-j, w/2, new Complex(data[h-j][1], data[h-j][0])); + } + if (j == 0 && i == 0) { + m.setElement(0, 0, new Complex(data[0][0], 0)); + } + if (j == 0 && i == w/2) { + m.setElement(0, w/2, new Complex(data[0][1], 0)); + } + if (j == h/2 && i == 0) { + m.setElement(h/2, 0, new Complex(data[h/2][0], 0)); + } + if (j == h/2 && i == w/2) { + m.setElement(h/2, w/2, new Complex(data[h/2][1], 0)); + } + } + } + + return m; + } + + private ComplexMatrix FFTComplexArray2D_to_ComplexMatrix(double[][] data, int h, int w) { + + ComplexMatrix m = new ComplexMatrix(h,w); + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + m.setElement(j,i, new Complex(data[j][2*i], data[j][2*i+1])); + } + } + + return m; + } + + private double[][] ComplexMatrix_to_FFTArray2D(ComplexMatrix m) { + int w = m.getNcol(); + int h = m.getNrow(); + double[][] data = new double[h][w]; + + for (int j = 0; j < h; j++) { + for (int i = 0; i <= w/2; i++) { + if (j > 0 && i > 0 && i < w/2) { + data[j][2*i] = m.getElementReference(j,i).getReal(); + data[j][2*i+1] = m.getElementReference(j,i).getImag(); + } + if (j == 0 && i > 0 && i < w/2) { + data[0][2*i] = m.getElementReference(0,i).getReal(); + data[0][2*i+1] = m.getElementReference(0,i).getImag(); + } + if (i == 0 && j > 0 && j < h/2) { + data[j][0] = m.getElementReference(j,0).getReal(); + data[j][1] = m.getElementReference(j,0).getImag(); + data[h-j][1] = m.getElementReference(j,w/2).getReal(); + data[h-j][0] = m.getElementReference(h-j,w/2).getImag(); + } + if (j == 0 && i == 0) { + data[0][0] = m.getElementReference(0,0).getReal(); + } + if (j == 0 && i == w/2) { + data[0][1] = m.getElementReference(0,w/2).getReal(); + } + if (j == h/2 && i == 0) { + data[h/2][0] = m.getElementReference(h/2,0).getReal(); + } + if (j == h/2 && i == w/2) { + data[h/2][1] = m.getElementReference(h/2,w/2).getReal(); + } + } + } + + return data; + } + + // convert a Complex Matrix into an 2d real part array data[0][][] + // and 2d imaginary part data[1][][] + private double[][][] ComplexMatrix_to_RealArray2D(ComplexMatrix m) { + int w = m.getNcol(); + int h = m.getNrow(); + double[][][] data = new double[2][h][w]; + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + data[0][j][i] = m.getElementReference(j,i).getReal(); + data[1][j][i] = m.getElementReference(j,i).getImag(); + } + } + + return data; + } +} \ No newline at end of file diff --git a/plugins/Align_ComputeShifts2.java b/plugins/Align_ComputeShifts2.java new file mode 100644 index 0000000..c53a0f5 --- /dev/null +++ b/plugins/Align_ComputeShifts2.java @@ -0,0 +1,701 @@ +import ij.*; +import ij.process.*; +import ij.gui.*; +import java.awt.*; +import ij.plugin.PlugIn; +import ij.WindowManager; +//import edu.emory.mathcs.jtransforms.fft.*; +//import edu.emory.mathcs.utils.*; +import org.jtransforms.fft.*; +import org.jtransforms.utils.*; +import flanagan.complex.*; +import flanagan.math.*; +import ij.plugin.frame.RoiManager; +import ij.gui.Roi; + + +public class Align_ComputeShifts2 implements PlugIn { + protected ImagePlus imp_r, imp_i; + protected int reference_slide; + protected Roi roi; + protected int usfac; + protected boolean debug = true; + double[][] shifts; + boolean allShifts; + + public void setup(int upscaleFactor, boolean allShifts, ImagePlus imp_r, ImagePlus imp_i, + int reference_slide, Roi roi) { + if (imp_r==null){ + throw new RuntimeException("Real part image must exist!"); + } + + if (roi==null){ + roi = new Roi(0,0,imp_r.getWidth(), imp_r.getHeight()); + } + Rectangle box = roi.getBounds(); + if (!ConcurrencyUtils.isPowerOf2(box.height) + || !ConcurrencyUtils.isPowerOf2(box.width)) { + throw new RuntimeException("The selected ROI height and with must be a power of 2"); + } + + this.usfac = upscaleFactor; + this.allShifts = allShifts; + this.imp_r = imp_r; + this.imp_i = imp_i; + this.reference_slide=reference_slide; + this.roi = roi; + + } + + public void run(String arg) { + if (allShifts) { + calculateAllShiftsRun(); + + } else { + calculateShiftsRun(); + } + return; + } + + private void calculateShiftsRun() { + // perform the FFT of each slice + + IJ.showStatus("1/2 Perform FFT of each slice"); + ComplexMatrix[] ffts = computeFFT(); + + // calculate shifts + IJ.showStatus("2/2 Calculate shifts between slices"); + shifts = calculateShifts(ffts); + + /* + // save shifts + ShiftsIO sio = new ShiftsIO(); + sio.save(shifts, "directshifts"); + */ + } + + public double[][] getShifts(){ + return shifts; + } + + private void calculateAllShiftsRun() { + // perform the FFT of each slice + IJ.showStatus("1/2 Perform FFT of each slice"); + ComplexMatrix[] ffts = computeFFT(); + + // calculate shifts + IJ.showStatus("2/2 Calculate shifts between slices"); + double[][] shifts = calculateAllShifts(ffts); + // save shifts + ShiftsIO sio = new ShiftsIO(); + sio.save(shifts, "allshifts"); + } + + // perform the FFT of each slice + private ComplexMatrix[] computeFFT() { + + int slices = imp_r.getStackSize(); + + ComplexMatrix[] ffts = new ComplexMatrix[slices]; + for (int i=1; i <= slices; i++) { + if (imp_i == null) { + ImageProcessor ip = imp_r.getStack().getProcessor(i); + ip.setRoi(roi); + ImageProcessor curr = ip.crop().convertToFloat(); + double[][] data = ImageProcessor_to_FFTArray2D(curr); + ffts[i-1] = fft2(data); + } else { + ImageProcessor ip1, ip2; + ip1 = imp_r.getStack().getProcessor(i); + ip1.setRoi(roi); + ImageProcessor curr_r = ip1.crop().convertToFloat(); + ip2 = imp_i.getStack().getProcessor(i); + ip2.setRoi(roi); + ImageProcessor curr_i = ip2.crop().convertToFloat(); + double[][] data = ImageProcessor_to_FFTComplexArray2D(curr_r, curr_i); + ffts[i-1] = cfft2(data); + } + IJ.showProgress(i, slices); + } + + return ffts; + } + + //calculate the shifts between ffts + private double[][] calculateShifts(ComplexMatrix[] ffts) { + + double[][] shifts = new double[ffts.length][6]; + for (int i = 0; i < ffts.length; i++) { + shifts[i][0] = reference_slide; shifts[i][1] = i+1; + double[] temp = DFTRegistration(ffts[reference_slide - 1], ffts[i]); + shifts[i][2] = temp[2]; shifts[i][3] = temp[3]; + shifts[i][4] = temp[0]; shifts[i][5] = temp[1]; + IJ.showProgress(i + 1, ffts.length); + } + return shifts; // [ref, drifted, dr, dc, error, diffphase] + + } + + //calculate all the shifts between ffts + private double[][] calculateAllShifts(ComplexMatrix[] ffts) { + + double[][] shifts = new double[ffts.length*(ffts.length-1)/2][6]; + int id = 0; + for (int i = 0; i < ffts.length-1; i++) { + for (int j = i+1; j < ffts.length; j++) { + shifts[id][0] = i+1; shifts[id][1] = j+1; + double[] temp = DFTRegistration(ffts[i], ffts[j]); + shifts[id][2] = temp[2]; shifts[id][3] = temp[3]; + shifts[id][4] = temp[0]; shifts[id][5] = temp[1]; + id = id + 1; + IJ.showProgress(id + 1, ffts.length*(ffts.length-1)/2); + } + } + + return shifts; // [ref,drifted,dr,dc,error, diffphase] + } + + // compute 2D fft from an image + private ComplexMatrix fft2(double[][] data) { + + int h = data.length; + int w = data[0].length; + DoubleFFT_2D fft = new DoubleFFT_2D(h, w); + + fft.realForward(data); + ComplexMatrix m = FFTArray2D_to_ComplexMatrix(data, h, w); + + return m; + } + + // compute complex 2D fft from an image + private ComplexMatrix cfft2(double[][] data) { + + int h = data.length; + int w = data[0].length; + DoubleFFT_2D fft = new DoubleFFT_2D(h, w/2); + + fft.complexForward(data); + ComplexMatrix m = FFTComplexArray2D_to_ComplexMatrix(data, h, w/2); + + return m; + } + + // compute inverse 2D fft from a complex matrix + private double[][] ifft2(ComplexMatrix m) { + int w = m.getNcol(); + int h = m.getNrow(); + + DoubleFFT_2D fft = new DoubleFFT_2D(h, w); + + double[][] data = ComplexMatrix_to_FFTArray2D(m); + fft.realInverse(data, true); + + return data; + } + + // compute complex inverse 2D fft from a complex matrix + private ComplexMatrix cifft2(ComplexMatrix m) { + int w = m.getNcol(); + int h = m.getNrow(); + + DoubleFFT_2D fft = new DoubleFFT_2D(h, w); + + double[][] data = new double[h][2*w]; + for (int j=0; j m) { + peak[1] = peak[1] - mlarge; + } + if (peak[2] > n) { + peak[2] = peak[2] - nlarge; + } + + //% If upsampling > 2, then refine estimate with matrix multiply DFT + if (this.usfac > 2) { + // %%% DFT computation %%% + // % Initial shift estimate in upsampled grid + double row_shift = Math.round(peak[1]/2.0*this.usfac)/this.usfac; + double col_shift = Math.round(peak[2]/2.0*this.usfac)/this.usfac; + int dftshift = (int)Math.floor(Math.ceil(this.usfac*1.5)/2); // Center of output array at dftshift+1 + // % Matrix multiply DFT around the current shift estimate + ComplexMatrix in = ElementProduct(drifted, ref.conjugate()); + ComplexMatrix nCC = dftups(in, (int)Math.ceil(this.usfac*1.5), (int)Math.ceil(this.usfac*1.5), + dftshift-row_shift*this.usfac, dftshift-col_shift*this.usfac); + nCC = nCC.times(1.0/(m*n*this.usfac*this.usfac)).conjugate(); + // % Locate maximum and map back to original pixel grid + double[] npeak = cFindPeak(nCC); //max_r, max_i, r, c + + ComplexMatrix mrg00 = dftups(ElementProduct(ref, ref.conjugate()),1,1,0,0); + double rg00 = mrg00.getElementReference(0, 0).abs()/(m*n*this.usfac*this.usfac); + ComplexMatrix mrf00 = dftups(ElementProduct(drifted, drifted.conjugate()),1,1,0,0); + double rf00 = mrf00.getElementReference(0, 0).abs()/(m*n*this.usfac*this.usfac); + + npeak[1] = npeak[1] - dftshift; + npeak[2] = npeak[2] - dftshift; + output[0] = Math.sqrt(Math.abs(1.0 - npeak[0]*npeak[0]/(rg00*rf00))); //error + output[1] = Math.atan2(npeak[4], npeak[3]); //diffphase + output[2] = row_shift + npeak[1]/this.usfac; //delta row + output[3] = col_shift + npeak[2]/this.usfac; //delta col + + } else { + // % If upsampling = 2, no additional pixel shift refinement + double rg00 = SumSquareAbs(ref)/(mlarge*nlarge); + double rf00 = SumSquareAbs(drifted)/(mlarge*nlarge); + + output[0] = Math.sqrt(Math.abs(1.0 - peak[0]*peak[0]/(rg00*rf00))); //error + output[1] = Math.atan2(peak[4], peak[3]); //diffphase + output[2] = peak[1]/2.0; //delta row + output[3] = peak[2]/2.0; //delta col + } + + return output; + } + + private double SumSquareAbs(ComplexMatrix m) { + double sum = 0.0; + + for (int j = 0; j < m.getNrow(); j ++){ + for (int i = 0; i < m.getNcol(); i++) { + sum += m.getElementReference(j, i).squareAbs(); + } + } + + return sum; + } + + private double[] cFindPeak(ComplexMatrix m) { + double max = 0.0; + double realmax = 0.0; + double imagmax = 0.0; + int cmax = 0, rmax = 0; + + for (int j = 0; j < m.getNrow(); j ++){ + for (int i = 0; i < m.getNcol(); i++) { + if (m.getElementReference(j, i).abs() > max) { + max = m.getElementReference(j, i).abs(); + realmax = m.getElementReference(j, i).getReal(); + imagmax = m.getElementReference(j, i).getImag(); + rmax = j; + cmax = i; + } + } + } + + double[] res = new double[5]; + res[0] = Math.sqrt(realmax*realmax+imagmax*imagmax); res[1] = rmax; res[2] = cmax; + res[3] = realmax; res[4] = imagmax; + return res; + } + + private ComplexMatrix fftshift(ComplexMatrix in) { + int nc = in.getNcol(); + int nr = in.getNrow(); + + ComplexMatrix out = new ComplexMatrix (nr, nc); + + int midi = (int)Math.floor(nc/2.0); + int offi = (int)Math.ceil(nc/2.0); + int midj = (int)Math.floor(nr/2.0); + int offj = (int)Math.ceil(nr/2.0); + + for (int j = 0; j < nr; j ++){ + for (int i = 0; i < nc; i++) { + if (j < midj) { + if (i < midi) { + out.setElement(j, i, in.getElementReference(j+offj, i+offi)); + } else { + out.setElement(j, i, in.getElementReference(j+offj, i-midi)); + } + } else { + if (i < midi) { + out.setElement(j, i, in.getElementReference(j-midj, i+offi)); + } else { + out.setElement(j, i, in.getElementReference(j-midj, i-midi)); + } + } + } + } + + return out; + } + + private ComplexMatrix ifftshift(ComplexMatrix in) { + int nc = in.getNcol(); + int nr = in.getNrow(); + + ComplexMatrix out = new ComplexMatrix (nr, nc); + + int midi = (int)Math.ceil(nc/2.0); + int offi = (int)Math.floor(nc/2.0); + int midj = (int)Math.ceil(nr/2.0); + int offj = (int)Math.floor(nr/2.0); + + for (int j = 0; j < nr; j ++){ + for (int i = 0; i < nc; i++) { + if (j < midj) { + if (i < midi) { + out.setElement(j, i, in.getElementReference(j+offj, i+offi)); + } else { + out.setElement(j, i, in.getElementReference(j+offj, i-midi)); + } + } else { + if (i < midi) { + out.setElement(j, i, in.getElementReference(j-midj, i+offi)); + } else { + out.setElement(j, i, in.getElementReference(j-midj, i-midi)); + } + } + } + } + + return out; + } + + private Matrix ifftshift(Matrix in) { + int nc = in.getNcol(); + int nr = in.getNrow(); + + Matrix out = new Matrix (nr, nc); + + int midi = (int)Math.ceil(nc/2.0); + int offi = (int)Math.floor(nc/2.0); + int midj = (int)Math.ceil(nr/2.0); + int offj = (int)Math.floor(nr/2.0); + + for (int j = 0; j < nr; j ++){ + for (int i = 0; i < nc; i++) { + if (j < midj) { + if (i < midi) { + out.setElement(j, i, in.getElement(j+offj, i+offi)); + } else { + out.setElement(j, i, in.getElement(j+offj, i-midi)); + } + } else { + if (i < midi) { + out.setElement(j, i, in.getElement(j-midj, i+offi)); + } else { + out.setElement(j, i, in.getElement(j-midj, i-midi)); + } + } + } + } + + return out; + } + + public Matrix times(Matrix amat, Matrix bmat){ + + if(amat.getNumberOfColumns()!=bmat.getNumberOfRows())throw new IllegalArgumentException("Nonconformable matrices"); + + Matrix cmat = new Matrix(amat.getNumberOfRows(), bmat.getNumberOfColumns()); + double [][] aarray = amat.getArrayReference(); + double [][] barray = bmat.getArrayReference(); + double [][] carray = cmat.getArrayReference(); + double sum = 0.0D; + + for(int i=0; i 0 && i > 0 && i < w/2) { + m.setElement(j,i, new Complex(data[j][2*i], data[j][2*i+1])); + m.setElement(h-j, w-i, new Complex(data[j][2*i], -data[j][2*i+1])); + } + if (j == 0 && i > 0 && i < w/2) { + m.setElement(0, i, new Complex(data[0][2*i], data[0][2*i+1])); + m.setElement(0, w-i, new Complex(data[0][2*i], -data[0][2*i+1])); + } + if (i == 0 && j > 0 && j < h/2) { + m.setElement(j,0, new Complex(data[j][0], data[j][1])); + m.setElement(h-j, 0, new Complex(data[j][0], -data[j][1])); + m.setElement(j, w/2, new Complex(data[h-j][1], -data[h-j][0])); + m.setElement(h-j, w/2, new Complex(data[h-j][1], data[h-j][0])); + } + if (j == 0 && i == 0) { + m.setElement(0, 0, new Complex(data[0][0], 0)); + } + if (j == 0 && i == w/2) { + m.setElement(0, w/2, new Complex(data[0][1], 0)); + } + if (j == h/2 && i == 0) { + m.setElement(h/2, 0, new Complex(data[h/2][0], 0)); + } + if (j == h/2 && i == w/2) { + m.setElement(h/2, w/2, new Complex(data[h/2][1], 0)); + } + } + } + + return m; + } + + private ComplexMatrix FFTComplexArray2D_to_ComplexMatrix(double[][] data, int h, int w) { + + ComplexMatrix m = new ComplexMatrix(h,w); + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + m.setElement(j,i, new Complex(data[j][2*i], data[j][2*i+1])); + } + } + + return m; + } + + private double[][] ComplexMatrix_to_FFTArray2D(ComplexMatrix m) { + int w = m.getNcol(); + int h = m.getNrow(); + double[][] data = new double[h][w]; + + for (int j = 0; j < h; j++) { + for (int i = 0; i <= w/2; i++) { + if (j > 0 && i > 0 && i < w/2) { + data[j][2*i] = m.getElementReference(j,i).getReal(); + data[j][2*i+1] = m.getElementReference(j,i).getImag(); + } + if (j == 0 && i > 0 && i < w/2) { + data[0][2*i] = m.getElementReference(0,i).getReal(); + data[0][2*i+1] = m.getElementReference(0,i).getImag(); + } + if (i == 0 && j > 0 && j < h/2) { + data[j][0] = m.getElementReference(j,0).getReal(); + data[j][1] = m.getElementReference(j,0).getImag(); + data[h-j][1] = m.getElementReference(j,w/2).getReal(); + data[h-j][0] = m.getElementReference(h-j,w/2).getImag(); + } + if (j == 0 && i == 0) { + data[0][0] = m.getElementReference(0,0).getReal(); + } + if (j == 0 && i == w/2) { + data[0][1] = m.getElementReference(0,w/2).getReal(); + } + if (j == h/2 && i == 0) { + data[h/2][0] = m.getElementReference(h/2,0).getReal(); + } + if (j == h/2 && i == w/2) { + data[h/2][1] = m.getElementReference(h/2,w/2).getReal(); + } + } + } + + return data; + } + + // convert a Complex Matrix into an 2d real part array data[0][][] + // and 2d imaginary part data[1][][] + private double[][][] ComplexMatrix_to_RealArray2D(ComplexMatrix m) { + int w = m.getNcol(); + int h = m.getNrow(); + double[][][] data = new double[2][h][w]; + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + data[0][j][i] = m.getElementReference(j,i).getReal(); + data[1][j][i] = m.getElementReference(j,i).getImag(); + } + } + + return data; + } +} \ No newline at end of file diff --git a/plugins/Align_TranslationFilter.java b/plugins/Align_TranslationFilter.java new file mode 100644 index 0000000..2ed978a --- /dev/null +++ b/plugins/Align_TranslationFilter.java @@ -0,0 +1,115 @@ +import ij.plugin.filter.ExtendedPlugInFilter; +import ij.plugin.filter.PlugInFilterRunner; +import ij.plugin.filter.GaussianBlur; +import ij.*; +import ij.process.*; + +import ij.io.OpenDialog; +import ij.io.SaveDialog; +import java.util.ArrayList; +import java.io.IOException; +import java.io.File; +import jmatio.types.*; +import jmatio.io.*; +import ij.gui.GenericDialog; +import ij.IJ; +import ij.Prefs; + +public class Align_TranslationFilter implements ExtendedPlugInFilter { + private double[][] shifts; + private final int flags = (DOES_ALL-DOES_RGB)|DOES_STACKS|NO_CHANGES|FINAL_PROCESSING; + private ImagePlus imp; + private ImagePlus registred; + private ImageStack translated; + private PlugInFilterRunner pifr; + private int nbslices = 0; + private int processed = 0; + + public void setShifts(double[][] shifts){ + this.shifts = shifts; + } + + public double[][] getShifts(){ + return shifts; + } + + public ImagePlus getOutput(){ + return registred; + } + + public void setImp(ImagePlus imp){ + this.imp = imp; + } + + /** + * This method is called by ImageJ for initialization. + * @param arg Unused here. For plugins in a .jar file this argument string can + * be specified in the plugins.config file of the .jar archive. + * @param imp The ImagePlus containing the image (or stack) to process. + * @return The method returns flags (i.e., a bit mask) specifying the + * capabilities (supported formats, etc.) and needs of the filter. + * See PlugInFilter.java and ExtendedPlugInFilter in the ImageJ + * sources for details. + */ + public int setup(String arg, ImagePlus imp) { + System.out.println("Setup " + arg); + if ("final".equals(arg)) { + registred.setStack("REG_" + this.imp.getTitle(), translated); + return DONE; + } else { + if (imp==null ){ + this.imp = imp; + } + return flags; + } + } + + /** Called by ImageJ after setup. */ + public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) { + pifr = pfr; + + //ShiftsIO sio = new ShiftsIO(); + //shifts = sio.load(null, "directshifts"); + //if (shifts == null) return DONE; + + //IJ.register(this.getClass()); // protect static class variables (filter parameters) from garbage collection + //return IJ.setupDialog(imp, flags); // ask whether to process all slices of stack (if a stack) + return flags; + } + + /** Process a FloatProcessor (with the CONVERT_TO_FLOAT flag, ImageJ does the conversion to float). + * Called by ImageJ for each stack slice (when processing a full stack); for RGB also called once for each color. */ + public void run(ImageProcessor ip) { + // translate from shifts + if (Thread.currentThread().isInterrupted()) return; + + int thisone = pifr.getSliceNumber(); + + ImageProcessor nip = ip.duplicate().convertToFloat(); + nip.setInterpolationMethod(ImageProcessor.BICUBIC); + if (shifts.length != nbslices) { + nip.translate(shifts[1][3], shifts[1][2]); // translate all the frame by the + // same shifts + } else { + nip.translate(shifts[thisone-1][3], shifts[thisone-1][2]); + } + + String lbl = imp.getStack().getSliceLabel(thisone); + if (lbl != null) { + translated.addSlice(lbl, nip, thisone - 1); + } else { + translated.addSlice("" + thisone, nip, thisone - 1); + } + translated.deleteSlice(thisone + 1); + + processed++; + IJ.showProgress(processed, nbslices); + } + + /** Called by ImageJ to set the number of calls to run(ip) corresponding to 100% of the progress bar */ + public void setNPasses(int nPasses) { + nbslices = nPasses; + registred = imp.createImagePlus(); + translated = new ImageStack(imp.getWidth(), imp.getHeight(), nbslices); + } +} \ No newline at end of file diff --git a/plugins/Align_TranslationFilter2.java b/plugins/Align_TranslationFilter2.java new file mode 100644 index 0000000..b42d69b --- /dev/null +++ b/plugins/Align_TranslationFilter2.java @@ -0,0 +1,91 @@ +import ij.plugin.filter.PlugInFilter; +import ij.plugin.filter.PlugInFilterRunner; +import ij.plugin.filter.GaussianBlur; +import ij.*; +import ij.process.*; +import com.jmatio.types.*; +import com.jmatio.io.*; + +public class Align_TranslationFilter2 implements PlugInFilter { + private final int flags = (DOES_ALL-DOES_RGB)|DOES_STACKS|NO_CHANGES|FINAL_PROCESSING; + private ImagePlus imp; + private ImagePlus registred; + private ImageStack translated; + private int nbslices = 0; + private int processed = 0; + + double[][] shifts; + + public void setShifts(double[][] shifts){ + this.shifts = shifts; + } + + public double[][] getShifts(){ + return this.shifts; + } + + + + /** + * This method is called by ImageJ for initialization. + * @param arg Unused here. For plugins in a .jar file this argument string can + * be specified in the plugins.config file of the .jar archive. + * @param imp The ImagePlus containing the image (or stack) to process. + * @return The method returns flags (i.e., a bit mask) specifying the + * capabilities (supported formats, etc.) and needs of the filter. + * See PlugInFilter.java and ExtendedPlugInFilter in the ImageJ + * sources for details. + */ + + public int setup(String arg, ImagePlus imp) { + nbslices = imp.getImageStackSize(); + registred = imp.createImagePlus(); + translated = new ImageStack(imp.getWidth(), imp.getHeight(), nbslices); + this.imp = imp; + + + if (shifts == null) return DONE; + //int ret = IJ.setupDialog(imp, flags); // ask whether to process all slices of stack (if a stack) + return flags; + } + + public ImagePlus getRegistred(){ + return registred; + } + + public ImageStack getTranslated(){ + return translated; + } + + /** Process a FloatProcessor (with the CONVERT_TO_FLOAT flag, ImageJ does the conversion to float). + * Called by ImageJ for each stack slice (when processing a full stack); for RGB also called once for each color. */ + public void run(ImageProcessor ip) { + + for (int slice =1; slice<= nbslices; slice++){ + if (Thread.currentThread().isInterrupted()) return; + + ImageProcessor nip = ip.duplicate().convertToFloat(); + nip.setInterpolationMethod(ImageProcessor.BICUBIC); + if (shifts.length != nbslices) { + nip.translate(shifts[1][3], shifts[1][2]); // translate all the frame by the + // same shifts + } else { + nip.translate(shifts[slice-1][3], shifts[slice-1][2]); + } + + String lbl = imp.getStack().getSliceLabel(slice); + if (lbl != null) { + translated.addSlice(lbl, nip, slice - 1); + } else { + translated.addSlice("" + slice, nip, slice - 1); + } + translated.deleteSlice(slice + 1); + + processed++; + IJ.showProgress(processed, nbslices); + } + registred.setStack("REG_" + imp.getTitle(), translated); + + } + +} \ No newline at end of file diff --git a/plugins/EnergyScan.form b/plugins/EnergyScan.form index c98bea0..759c47f 100755 --- a/plugins/EnergyScan.form +++ b/plugins/EnergyScan.form @@ -1,588 +1,588 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/MXSC-1.15.0.jar b/plugins/MXSC-1.15.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..50bdac077d342ca5c443c1c3cd153f4844a96527 GIT binary patch literal 292171 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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ diff --git a/plugins/SIStem.java b/plugins/SIStem.java new file mode 100644 index 0000000..519c0f8 --- /dev/null +++ b/plugins/SIStem.java @@ -0,0 +1,813 @@ +import ch.psi.pshell.core.JsonSerializer; +import ch.psi.pshell.device.Positioner; +import ch.psi.pshell.ui.Panel; +import ch.psi.pshell.ui.PanelProcessor; +import ch.psi.pshell.ui.QueueProcessor; +import ch.psi.utils.Arr; +import ch.psi.utils.IO; +import ch.psi.utils.State; +import ch.psi.utils.swing.SwingUtils; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.DnDConstants; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.DropMode; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JTable; +import javax.swing.TransferHandler; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.DefaultTableModel; + +/** + * + */ +public class SIStem extends PanelProcessor { + File currentFile; + final DefaultTableModel modelInactive; + final DefaultTableModel modelFixed; + final DefaultTableModel modelScanned; + + + public static final String FILE_EXTENSION = "json"; + + public SIStem() { + initComponents(); + modelInactive = (DefaultTableModel) tableInactive.getModel(); + modelFixed = (DefaultTableModel) tableFixed.getModel(); + modelScanned = (DefaultTableModel) tableScanned.getModel(); + clear(); + Standard st = new Standard(); st.run(); + + + abstract class PositinerTransferHandler extends TransferHandler{ + @Override + public int getSourceActions(JComponent c) { + return DnDConstants.ACTION_COPY_OR_MOVE; + } + @Override + public Transferable createTransferable(JComponent comp) { + try{ + JTable table = (JTable) comp; + return new StringSelection((String) table.getModel().getValueAt(table.getSelectedRow(),0)); + } catch(Exception ex){ + return null; + } + } + @Override + public boolean canImport(TransferHandler.TransferSupport support) { + return support.isDataFlavorSupported(DataFlavor.stringFlavor); + } + @Override + public boolean importData(TransferHandler.TransferSupport support) { + if (support.isDrop() && canImport(support)) { + try { + JTable.DropLocation dl = (JTable.DropLocation)support.getDropLocation(); + String name = (String) support.getTransferable().getTransferData(DataFlavor.stringFlavor); + Positioner pos = getContext().getDevicePool().getByName(name, Positioner.class); + DefaultTableModel model = ((DefaultTableModel)((JTable)support.getComponent()).getModel()); + addPositioner(pos, model, dl.getRow(), getRowData(pos)); + } catch (Exception ex) { + return false; + } + } + return true; + } + abstract Object[] getRowData(Positioner pos); + }; + + tableInactive.setDragEnabled(true); + tableInactive.setDropMode(DropMode.INSERT_ROWS); + tableInactive.setFillsViewportHeight(true); + tableInactive.setTransferHandler(new PositinerTransferHandler() { + @Override + Object[] getRowData(Positioner pos) { + return new Object[]{pos.getName()}; + } + }); + + tableFixed.setDragEnabled(true); + tableFixed.setDropMode(DropMode.INSERT_ROWS); + tableFixed.setFillsViewportHeight(true); + tableFixed.setTransferHandler(new PositinerTransferHandler() { + @Override + Object[] getRowData(Positioner pos) { + return new Object[]{pos.getName(), Double.NaN, pos.getUnit()}; + } + }); + + tableScanned.setDragEnabled(true); + tableScanned.setDropMode(DropMode.INSERT_ROWS); + tableScanned.setFillsViewportHeight(true); + tableScanned.setTransferHandler(new PositinerTransferHandler() { + @Override + Object[] getRowData(Positioner pos) { + return new Object[]{pos.getName(), Double.NaN, Double.NaN, 0, Double.NaN, pos.getUnit()}; + } + }); + } + + void addPositioner(Positioner pos, DefaultTableModel model, int row, Object[] data){ + if (row<0){ + row = model.getRowCount(); + } + model.insertRow(row, data); + removePositioner(pos, modelInactive, (model==modelInactive) ? row : -1); + removePositioner(pos, modelFixed, (model==modelFixed) ? row : -1); + removePositioner(pos, modelScanned, (model==modelScanned) ?row : -1); + } + + void removePositioner(Positioner pos, DefaultTableModel model, int except){ + for (int i=0; i< model.getRowCount(); i++){ + if ((except<0) || (i!=except)){ + if (model.getValueAt(i, 0).equals(pos.getName())){ + model.removeRow(i); + break; + } + } + } + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + + } + + + @Override + public void onStateChange(State state, State former) { + updateControls(); + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + + //Callback to perform update - in event thread + @Override + protected void doUpdate() { + } + + + @Override + public String getFileName(){ + return (currentFile==null) ? null : currentFile.toString(); + } + + @Override + public boolean isTabNameUpdated() { + return false; + } + + @Override + public String getType() { + return "SIStem"; + } + + @Override + public boolean createMenuNew() { + return true; + } + + @Override + public boolean createFilePanel() { + return true; + } + + @Override + public String getDescription() { + return "SIStem Scan definition file (*." + FILE_EXTENSION + ")"; + } + + @Override + public String[] getExtensions() { + return new String[]{FILE_EXTENSION}; + } + + @Override + public String getHomePath() { + return "{home}/scans"; + } + + @Override + public void saveAs(String fileName) throws IOException { + currentFile = new File(fileName); + Map preActions = new HashMap(); + for (int i=0; i< modelFixed.getRowCount(); i++){ + preActions.put(modelFixed.getValueAt(i, 0), modelFixed.getValueAt(i, 1)); + } + String[] positioners = new String[modelScanned.getRowCount()]; + Double[] start = new Double[modelScanned.getRowCount()]; + Double[] stop = new Double[modelScanned.getRowCount()]; + Integer[] steps = new Integer[modelScanned.getRowCount()]; + for (int i=0; i< modelScanned.getRowCount(); i++){ + positioners[i] = (String) modelScanned.getValueAt(i, 0); + start[i] = (Double) modelScanned.getValueAt(i, 1); + stop[i] = (Double) modelScanned.getValueAt(i, 2); + steps[i] = (Integer) modelScanned.getValueAt(i, 3) -1; + } + + Map config = new HashMap(); + config.put("PRE_ACTIONS", preActions); + config.put("POSITIONERS", positioners); + config.put("START", start); + config.put("STOP", stop); + config.put("STEPS", steps); + + //TODO: + config.put("SENSORS", Arr.toList(new String[]{"scienta.dataMatrix", "sin"})); + config.put("SETTLING_TIME", spinnerLatency.getValue() ); + config.put("PASSES", spinnerPasses.getValue()); + config.put("ZIGZAG", checkZigzag.isSelected()); + config.put("COMPRESSION", true); + + String json = JsonSerializer.encode(config, true); + Files.write(currentFile.toPath(), json.getBytes()); + updateControls(); + } + + @Override + public void open(String fileName) throws IOException { + clear(); + if (fileName!=null){ + Path path = Paths.get(fileName); + String json = new String(Files.readAllBytes(path)); + currentFile = path.toFile(); + Map config = (Map) JsonSerializer.decode(json, Map.class); + + Map preActions = (Map) config.get("PRE_ACTIONS"); + + List positioners = (List)config.get("POSITIONERS"); + List start = (List)config.get("START"); + List stop = (List)config.get("STOP"); + List steps = (List) config.get("STEPS"); + + for (String name:preActions.keySet()){ + Positioner pos = getContext().getDevicePool().getByName(name, Positioner.class); + if (pos!=null){ + addPositioner(pos, modelFixed, -1, new Object[]{name, preActions.get(name), pos.getUnit()}); + } + } + for (int i=0; i< positioners.size(); i++){ + String name = positioners.get(i); + Positioner pos = getContext().getDevicePool().getByName(name, Positioner.class); + if (pos!=null){ + addPositioner(pos, modelScanned, -1, new Object[]{name, start.get(i), stop.get(i), steps.get(i), 0, pos.getUnit()}); + } + } + spinnerLatency.setValue(config.get("SETTLING_TIME")); + spinnerPasses.setValue(config.get("PASSES")); + checkZigzag.setSelected((Boolean) config.get("ZIGZAG")); + } + updateControls(); + } + + public void clear(){ + currentFile = null; + modelInactive.setRowCount(0); + modelFixed.setRowCount(0); + modelScanned.setRowCount(0); + Positioner[] positioners = getContext().getDevicePool().getAllDevicesOrderedByName(Positioner.class); + for (Positioner pos: positioners){ + modelInactive.addRow(new Object[]{pos.getName()}); + } + updateControls(); + } + + @Override + public void execute() throws Exception { + checkValues(); + + HashMap args = new HashMap(); + if (currentFile == null) { + throw new Exception("TODO"); + } + args.put("NAME", getScanName()); + + this.runAsync("SIStem", args).handle((ret, ex) -> { + if (ex != null) { + } + try { + } catch (Exception e) { + Logger.getLogger(SIStem.class.getName()).log(Level.SEVERE, null, e); + } + return ret; + }); + + } + + String getScanName() { + if (currentFile != null) { + return IO.getPrefix(currentFile); + } + return null; + } + + void updateControls() { + State state = getState(); + try { + textFileId.setText(String.valueOf(getContext().getFileSequentialNumber())); + } catch (Exception ex) { + textFileId.setText(""); + } + + String fileName = getFileName(); + if (fileName == null){ + textFile.setText(""); + } else { + String home = getContext().getSetup().expandPath(getHomePath()); + if (IO.isSubPath(fileName, home)) { + fileName = IO.getRelativePath(fileName, home); + } + textFile.setText(fileName); + } + + buttonStart.setEnabled((state == State.Ready) && (currentFile != null)); + buttonAddToQueue.setEnabled((state == State.Ready) && (currentFile != null)); + buttonAbort.setEnabled(state.isProcessing()); + buttonScienta.setEnabled(state.isInitialized()); + + } + + void checkValues(){ + for (int i=0; i< modelFixed.getRowCount(); i++){ + if (Double.isNaN((Double)modelFixed.getValueAt(i, 1))){ + throw new IllegalArgumentException("Invalid value for " + modelFixed.getValueAt(i, 0)); + } + } + for (int i=0; i< modelScanned.getRowCount(); i++){ + if (Double.isNaN((Double)modelScanned.getValueAt(i, 1))){ + throw new IllegalArgumentException("Invalid start for " + modelFixed.getValueAt(i, 0)); + } + if (Double.isNaN((Double)modelScanned.getValueAt(i, 2))){ + throw new IllegalArgumentException("Invalid stop for " + modelFixed.getValueAt(i, 0)); + } + if (((Integer)modelScanned.getValueAt(i, 3)) < 1){ + throw new IllegalArgumentException("Invalid points for " + modelFixed.getValueAt(i, 0)); + } + } + } + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonScienta = new javax.swing.JButton(); + jLabel2 = new javax.swing.JLabel(); + textFileId = new javax.swing.JTextField(); + buttonResetId = new javax.swing.JButton(); + buttonStart = new javax.swing.JButton(); + buttonAbort = new javax.swing.JButton(); + jPanel2 = new javax.swing.JPanel(); + buttonOpen = new javax.swing.JButton(); + buttonSave = new javax.swing.JButton(); + jLabel3 = new javax.swing.JLabel(); + textFile = new javax.swing.JTextField(); + buttonClear = new javax.swing.JButton(); + jPanel1 = new javax.swing.JPanel(); + jScrollPane1 = new javax.swing.JScrollPane(); + tableInactive = new javax.swing.JTable(); + jScrollPane2 = new javax.swing.JScrollPane(); + tableFixed = new javax.swing.JTable(); + jScrollPane3 = new javax.swing.JScrollPane(); + tableScanned = new javax.swing.JTable(); + jLabel1 = new javax.swing.JLabel(); + spinnerPasses = new javax.swing.JSpinner(); + checkZigzag = new javax.swing.JCheckBox(); + jLabel4 = new javax.swing.JLabel(); + spinnerLatency = new javax.swing.JSpinner(); + buttonAddToQueue = new javax.swing.JButton(); + + buttonScienta.setText("Scienta Panel"); + buttonScienta.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonScientaActionPerformed(evt); + } + }); + + jLabel2.setText("File ID:"); + + textFileId.setEditable(false); + textFileId.setHorizontalAlignment(javax.swing.JTextField.CENTER); + + buttonResetId.setText("Reset"); + buttonResetId.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonResetIdActionPerformed(evt); + } + }); + + buttonStart.setText("Start"); + buttonStart.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonStartActionPerformed(evt); + } + }); + + buttonAbort.setText("Abort"); + buttonAbort.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonAbortActionPerformed(evt); + } + }); + + jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("Parameters")); + + buttonOpen.setText("Open"); + buttonOpen.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonOpenActionPerformed(evt); + } + }); + + buttonSave.setText("Save"); + buttonSave.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonSaveActionPerformed(evt); + } + }); + + jLabel3.setText("File:"); + + textFile.setDisabledTextColor(javax.swing.UIManager.getDefaults().getColor("TextField.foreground")); + textFile.setEnabled(false); + + buttonClear.setText("Clear"); + buttonClear.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonClearActionPerformed(evt); + } + }); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Positioners")); + + jScrollPane1.setBorder(javax.swing.BorderFactory.createTitledBorder("Inactive")); + + tableInactive.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + "Name" + } + ) { + Class[] types = new Class [] { + java.lang.String.class + }; + boolean[] canEdit = new boolean [] { + false + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return canEdit [columnIndex]; + } + }); + tableInactive.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(tableInactive); + + jScrollPane2.setBorder(javax.swing.BorderFactory.createTitledBorder("Fixed")); + + tableFixed.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + "Name", "Value", "Units" + } + ) { + Class[] types = new Class [] { + java.lang.String.class, java.lang.Double.class, java.lang.String.class + }; + boolean[] canEdit = new boolean [] { + false, true, false + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return canEdit [columnIndex]; + } + }); + tableFixed.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane2.setViewportView(tableFixed); + + jScrollPane3.setBorder(javax.swing.BorderFactory.createTitledBorder("Scanned")); + + tableScanned.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + "Name", "Start", "Stop", "Points", "Step", "Units" + } + ) { + Class[] types = new Class [] { + java.lang.String.class, java.lang.Double.class, java.lang.Double.class, java.lang.Integer.class, java.lang.Double.class, java.lang.String.class + }; + boolean[] canEdit = new boolean [] { + false, true, true, true, false, false + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return canEdit [columnIndex]; + } + }); + tableScanned.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane3.setViewportView(tableScanned); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane3, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 183, Short.MAX_VALUE) + .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 183, Short.MAX_VALUE)) + .addGap(12, 12, 12)) + ); + + jLabel1.setText("Passes:"); + + spinnerPasses.setModel(new javax.swing.SpinnerNumberModel(1, 1, 1000, 1)); + + checkZigzag.setText("Zigzag"); + + jLabel4.setText("Settling Time:"); + + spinnerLatency.setModel(new javax.swing.SpinnerNumberModel(0.0d, 0.0d, 1000.0d, 1.0d)); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(17, 17, 17) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(0, 98, Short.MAX_VALUE) + .addComponent(checkZigzag) + .addGap(18, 18, 18) + .addComponent(jLabel4) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerLatency, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerPasses, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(textFile) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonOpen) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonSave) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonClear))))) + .addContainerGap()) + ); + + jPanel2Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonClear, buttonOpen, buttonSave}); + + jPanel2Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {spinnerLatency, spinnerPasses}); + + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonOpen) + .addComponent(buttonSave) + .addComponent(jLabel3) + .addComponent(textFile, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonClear)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel4) + .addComponent(spinnerLatency, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(checkZigzag)) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(spinnerPasses, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(2, 2, 2) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(56, Short.MAX_VALUE)) + ); + + buttonAddToQueue.setText("Add to Queue"); + buttonAddToQueue.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonAddToQueueActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(buttonAddToQueue) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(buttonStart) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(buttonAbort) + .addContainerGap(137, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addComponent(buttonScienta) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(textFileId, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonResetId) + .addContainerGap()))) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonAbort, buttonAddToQueue, buttonStart}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonScienta) + .addComponent(buttonResetId) + .addComponent(jLabel2) + .addComponent(textFileId, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonStart) + .addComponent(buttonAbort) + .addComponent(buttonAddToQueue)) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void buttonScientaActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonScientaActionPerformed + try { + this.showDevicePanel("scienta"); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonScientaActionPerformed + + private void buttonResetIdActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonResetIdActionPerformed + try { + this.getContext().setFileSequentialNumber(0); + updateControls(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonResetIdActionPerformed + + private void buttonStartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonStartActionPerformed + try { + execute(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonStartActionPerformed + + private void buttonAbortActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonAbortActionPerformed + try { + abort(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonAbortActionPerformed + + private void buttonOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonOpenActionPerformed + try { + open(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonOpenActionPerformed + + private void buttonSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonSaveActionPerformed + try { + JFileChooser chooser = new JFileChooser(getContext().getSetup().expandPath(getHomePath())); + FileNameExtensionFilter filter = new FileNameExtensionFilter(getDescription(), getExtensions()); + chooser.setFileFilter(filter); + try { + if (currentFile != null) { + chooser.setSelectedFile(currentFile); + } + } catch (Exception ex) { + this.showException(ex); + } + int rVal = chooser.showSaveDialog(this); + if (rVal == JFileChooser.APPROVE_OPTION) { + String fileName = chooser.getSelectedFile().getAbsolutePath(); + if (IO.getExtension(chooser.getSelectedFile().getAbsolutePath()).isEmpty()) { + fileName += "." + FILE_EXTENSION; + } + saveAs(fileName); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonSaveActionPerformed + + private void buttonAddToQueueActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonAddToQueueActionPerformed + try { + QueueProcessor tq = null; + List queues = getView().getQueues(); + if (queues.size()==0){ + tq = getView().openProcessor(QueueProcessor.class, null); + } else { + tq = queues.get(0); + } + getView().getDocumentsTab().setSelectedComponent(tq); + tq.addNewFile(currentFile.getPath()); + + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonAddToQueueActionPerformed + + private void buttonClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonClearActionPerformed + try { + clear(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonClearActionPerformed + + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonAbort; + private javax.swing.JButton buttonAddToQueue; + private javax.swing.JButton buttonClear; + private javax.swing.JButton buttonOpen; + private javax.swing.JButton buttonResetId; + private javax.swing.JButton buttonSave; + private javax.swing.JButton buttonScienta; + private javax.swing.JButton buttonStart; + private javax.swing.JCheckBox checkZigzag; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane2; + private javax.swing.JScrollPane jScrollPane3; + private javax.swing.JSpinner spinnerLatency; + private javax.swing.JSpinner spinnerPasses; + private javax.swing.JTable tableFixed; + private javax.swing.JTable tableInactive; + private javax.swing.JTable tableScanned; + private javax.swing.JTextField textFile; + private javax.swing.JTextField textFileId; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/ScreenPanel.java b/plugins/ScreenPanel.java index 2413eee..0ca0bee 100644 --- a/plugins/ScreenPanel.java +++ b/plugins/ScreenPanel.java @@ -37,6 +37,7 @@ import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; +import javax.swing.SwingUtilities; /** * @@ -103,20 +104,30 @@ public class ScreenPanel extends Panel implements CamServerViewer.CamServerViewe if (App.hasArgument("instance_format")) { camServerViewer.setInstanceNameFormat(App.getArgumentValue("instance_format")); } + camServerViewer.setShowFit(true); + camServerViewer.setShowProfile(true); + camServerViewer.setShowReticle(true); } @Override public void onStart() { - super.onStart(); + super.onStart(); try { camServerViewer.setCameraServerUrl(App.getArgumentValue(ARG_CAMERA_SERVER)); camServerViewer.setPipelineServerUrl(App.getArgumentValue(ARG_PIPELINE_SERVER)); camServerViewer.setStartupStream(App.getArgumentValue(ARG_CAMERA)); - camServerViewer.initialize(CamServerViewer.SourceSelecionMode.Cameras); + SwingUtilities.invokeLater(()->{ + try { + camServerViewer.initialize(CamServerViewer.SourceSelecionMode.Cameras); + } catch (Exception ex) { + Logger.getLogger(ScreenPanel.class.getName()).log(Level.SEVERE, null, ex); + } + updateDialogTitle(); + }); + } catch (Exception ex) { logger.log(Level.SEVERE, null, ex); - } - updateDialogTitle(); + } } @Override @@ -195,7 +206,7 @@ public class ScreenPanel extends Panel implements CamServerViewer.CamServerViewe @Override public void onOpeningStream(String name) throws Exception { - System.out.println("Initializing stream " + name); + System.out.println("Initializing stream " + name); if ((devicesInitTask != null) && (devicesInitTask.isAlive())) { devicesInitTask.interrupt(); } @@ -207,6 +218,7 @@ public class ScreenPanel extends Panel implements CamServerViewer.CamServerViewe filter.close(); filter = null; } + updateDialogTitle(); } @@ -272,7 +284,7 @@ public class ScreenPanel extends Panel implements CamServerViewer.CamServerViewe devicesInitTask.start(); } } - + updateDialogTitle(); } public void onSavedSnapshot(String name, String instancee, String snapshotFile) throws Exception { diff --git a/plugins/ScreenPanel1.form b/plugins/ScreenPanel1.form new file mode 100755 index 0000000..3974d55 --- /dev/null +++ b/plugins/ScreenPanel1.form @@ -0,0 +1,960 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/ScreenPanel1.java b/plugins/ScreenPanel1.java new file mode 100755 index 0000000..7e1513c --- /dev/null +++ b/plugins/ScreenPanel1.java @@ -0,0 +1,3223 @@ +/* + * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.bs.CameraServer; +import ch.psi.pshell.core.Context; +import java.io.IOException; +import java.nio.file.Paths; +import javax.swing.DefaultComboBoxModel; +import ch.psi.pshell.ui.Panel; +import ch.psi.pshell.imaging.ImageListener; +import ch.psi.utils.State; +import ch.psi.utils.Chrono; +import ch.psi.utils.swing.SwingUtils; +import ch.psi.utils.swing.TextEditor; +import ch.psi.pshell.bs.PipelineServer; +import ch.psi.pshell.bs.StreamValue; +import ch.psi.pshell.core.JsonSerializer; +import ch.psi.pshell.data.DataManager; +import ch.psi.pshell.device.DescStatsDouble; +import ch.psi.pshell.device.Device; +import ch.psi.pshell.epics.ChannelInteger; +import ch.psi.pshell.epics.DiscretePositioner; +import ch.psi.pshell.epics.Epics; +import ch.psi.pshell.imaging.Calibration; +import ch.psi.pshell.imaging.Colormap; +import ch.psi.pshell.imaging.ColormapSource; +import ch.psi.pshell.imaging.ColormapSource.ColormapSourceConfig; +import ch.psi.pshell.ui.App; +import ch.psi.pshell.imaging.Data; +import ch.psi.pshell.imaging.DimensionDouble; +import ch.psi.pshell.imaging.Histogram; +import ch.psi.pshell.imaging.ImageBuffer; +import ch.psi.pshell.imaging.Overlay; +import ch.psi.pshell.imaging.Overlays; +import ch.psi.pshell.imaging.Overlays.Text; +import ch.psi.pshell.imaging.Pen; +import ch.psi.pshell.imaging.PointDouble; +import ch.psi.pshell.imaging.Renderer; +import ch.psi.pshell.imaging.RendererListener; +import ch.psi.pshell.imaging.RendererMode; +import ch.psi.pshell.imaging.Source; +import ch.psi.pshell.imaging.SourceBase; +import ch.psi.pshell.imaging.Utils; +import ch.psi.pshell.scripting.InterpreterResult; +import ch.psi.pshell.scripting.ScriptManager; +import ch.psi.pshell.swing.ValueSelection; +import ch.psi.pshell.swing.ValueSelection.ValueSelectionListener; +import ch.psi.utils.Arr; +import ch.psi.utils.ArrayProperties; +import ch.psi.utils.Convert; +import ch.psi.utils.swing.Editor.EditorDialog; +import ch.psi.utils.swing.MainFrame; +import ch.psi.utils.swing.StandardDialog; +import ch.psi.utils.swing.StandardDialog.StandardDialogListener; +import ch.psi.utils.swing.SwingUtils.OptionResult; +import ch.psi.utils.swing.SwingUtils.OptionType; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import javax.swing.table.DefaultTableModel; +import org.apache.commons.math3.analysis.function.Gaussian; +import org.apache.commons.math3.fitting.GaussianCurveFitter; +import org.apache.commons.math3.fitting.WeightedObservedPoint; +import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; + +/** + * + */ +public class ScreenPanel1 extends Panel { + + final String CAMERA_DEVICE_NAME = "CurrentCamera"; + boolean useServerStats = true; + String userOverlaysConfigFile; + ColormapSource camera; + PipelineServer server; + String cameraName; + int polling = 1000; + Overlay marker = null; + JDialog histogramDialog; + DiscretePositioner screen; + DiscretePositioner filter; + boolean showFit; + boolean showProfile; + Overlay[] userOv; + Overlay[] fitOv; + Overlay[] profileOv; + Overlay errorOverlay; + boolean requestCameraListUpdate; + boolean goodRegion; + String serverUrl; + String instanceName; + + boolean averaging = false; + + Double getServerDouble(String name) { + return (Double) Convert.toDouble(server.getValue(name)); + } + + double[] getServerDoubleArray(String name) { + return (double[]) Convert.toDouble(server.getValue(name)); + } + + Double getServerDouble(String name, StreamValue cache) { + return (Double) Convert.toDouble(cache.__getitem__(name)); + } + + double[] getServerDoubleArray(String name, StreamValue cache) { + return (double[]) Convert.toDouble(cache.__getitem__(name)); + } + + class ImageData { + + ImageData() { + if (server != null) { + cache = server.getStream().take(); + String prefix = goodRegion ? "gr_" : ""; + x_fit_mean = getServerDouble(prefix + "x_fit_mean", cache); + y_fit_mean = getServerDouble(prefix + "y_fit_mean", cache); + x_fit_standard_deviation = getServerDouble(prefix + "x_fit_standard_deviation", cache); + y_fit_standard_deviation = getServerDouble(prefix + "y_fit_standard_deviation", cache); + x_fit_gauss_function = getServerDoubleArray(prefix + "x_fit_gauss_function", cache); + y_fit_gauss_function = getServerDoubleArray(prefix + "y_fit_gauss_function", cache); + x_profile = getServerDoubleArray("x_profile", cache); + y_profile = getServerDoubleArray("y_profile", cache); + x_center_of_mass = getServerDouble("x_center_of_mass", cache); + y_center_of_mass = getServerDouble("y_center_of_mass", cache); + x_rms = getServerDouble("x_rms", cache); + y_rms = getServerDouble("y_rms", cache); + if (goodRegion) { + double[] gX2 = new double[x_profile.length]; + Arrays.fill(gX2, Double.NaN); + try { + double x = getServerDoubleArray("gr_x_axis", cache)[0]; + System.arraycopy(x_fit_gauss_function, 0, gX2, (int) ((renderer.getCalibration() != null) ? renderer.getCalibration().convertToImageX(x) : x), x_fit_gauss_function.length); + } catch (Exception ex) { + } + x_fit_gauss_function = gX2; + double[] gY2 = new double[y_profile.length]; + Arrays.fill(gY2, Double.NaN); + try { + double y = getServerDoubleArray("gr_y_axis", cache)[0]; + System.arraycopy(y_fit_gauss_function, 0, gY2, (int) ((renderer.getCalibration() != null) ? renderer.getCalibration().convertToImageY(y) : y), y_fit_gauss_function.length); + } catch (Exception ex) { + } + y_fit_gauss_function = gY2; + } + } + } + public Double x_fit_mean; + public Double y_fit_mean; + public Double x_center_of_mass; + public Double x_rms; + public Double x_fit_standard_deviation; + public Double y_fit_standard_deviation; + public Double y_center_of_mass; + public Double y_rms; + public double[] x_profile; + public double[] x_fit_gauss_function; + public double[] y_profile; + public double[] y_fit_gauss_function; + public StreamValue cache; + + } + + class Frame extends ImageData { + + Frame(Data data) { + this.data = data; + } + Data data; + + } + + final ArrayList imageBuffer = new ArrayList(); + Frame currentFrame; + int imageBufferLenght = 1; + Text imageBufferOverlay; + + public ScreenPanel1() { + initComponents(); + spinnerThreshold.setVisible(false); + btFixColormapRange.setVisible(false); + spinnerGrThreshold.setVisible(false); + labelGrThreshold.setVisible(false); + spinnerGrScale.setVisible(false); + labelGrScale.setVisible(false); + //spinnerMin.setVisible(false); labelMin.setVisible(false); + //spinnerMax.setVisible(false); labelMax.setVisible(false); + renderer.setPersistenceFile(Paths.get(getContext().getSetup().getContextPath(), "Renderer_Cameras.bin")); + setPersistedComponents(new Component[]{buttonServer, buttonDirect}); + comboCameras.setEnabled(false); + SwingUtils.setEnumCombo(comboColormap, Colormap.class); + if (App.hasArgument("poll")) { + try { + polling = Integer.valueOf(App.getArgumentValue("poll")); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + if (App.hasArgument("zoom")) { + try { + renderer.setDefaultZoom(Double.valueOf(App.getArgumentValue("zoom"))); + renderer.resetZoom(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + if (App.hasArgument("buf")) { + try { + imageBufferLenght = Integer.valueOf(App.getArgumentValue("buf")); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + if (App.hasArgument("usr_ov")) { + try { + userOverlaysConfigFile = App.getArgumentValue("usr_ov"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + if (App.hasArgument("srv_url")) { + serverUrl = App.getArgumentValue("srv_url"); + } + + if (App.hasArgument("calc")) { + useServerStats = false; + } + + renderer.setProfileNormalized(true); + renderer.setShowProfileLimits(false); + + JMenuItem menuCalibrate = new JMenuItem("Calibrate..."); + menuCalibrate.addActionListener((ActionEvent e) -> { + try { + calibrate(); + } catch (Exception ex) { + ex.printStackTrace(); + showException(ex); + } + }); + + JMenuItem menuShowStreamData = new JMenuItem("Show Stream Data"); + menuShowStreamData.addActionListener((ActionEvent e) -> { + try { + showStreamData(); + } catch (Exception ex) { + showException(ex); + } + }); + + JMenuItem menuSaveStack = new JMenuItem("Save Stack"); + menuSaveStack.addActionListener((ActionEvent e) -> { + try { + saveStack(); + } catch (Exception ex) { + showException(ex); + } + }); + menuSaveStack.setEnabled(imageBufferLenght > 0); + + JMenuItem menuSetROI = new JMenuItem("Set ROI..."); + menuSetROI.addActionListener((ActionEvent e) -> { + renderer.abortSelection(); + if (server != null) { + final Overlays.Rect selection = new Overlays.Rect(renderer.getPenMovingOverlay()); + renderer.addListener(new RendererListener() { + @Override + public void onSelectionFinished(Renderer renderer, Overlay overlay) { + try { + renderer.setShowReticle(false); + Rectangle roi = overlay.isFixed() ? renderer.toImageCoord(overlay.getBounds()) : overlay.getBounds(); + if (server.isRoiEnabled()) { + int[] cur = server.getRoi(); + server.setRoi(new int[]{roi.x + cur[0], roi.y + cur[1], roi.width, roi.height}); + } else { + server.setRoi(new int[]{roi.x, roi.y, roi.width, roi.height}); + } + } catch (Exception ex) { + } finally { + renderer.removeListener(this); + } + } + + @Override + public void onSelectionAborted(Renderer renderer, Overlay overlay) { + renderer.removeListener(this); + } + }); + selection.setFixed(true); + renderer.startSelection(selection); + } + }); + + JMenuItem menuResetROI = new JMenuItem("Reset ROI"); + menuResetROI.addActionListener((ActionEvent e) -> { + renderer.abortSelection(); + if (server != null) { + try { + renderer.setShowReticle(false); + server.resetRoi(); + } catch (IOException ex) { + showException(ex); + } + } + }); + + renderer.getPopupMenu().add(menuShowStreamData); + renderer.getPopupMenu().add(menuCalibrate); + renderer.getPopupMenu().add(menuSaveStack); + renderer.getPopupMenu().addSeparator(); + renderer.getPopupMenu().add(menuSetROI); + renderer.getPopupMenu().add(menuResetROI); + renderer.getPopupMenu().addPopupMenuListener(new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + menuResetROI.setEnabled(server != null); + menuSetROI.setEnabled(server != null); + menuShowStreamData.setVisible(server != null); + menuCalibrate.setVisible(server != null); + menuCalibrate.setEnabled((calibrationDialolg == null) || (!calibrationDialolg.isShowing())); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + } + }); + renderer.getPopupMenu().setVisible(false); + + showFit = buttonFit.isSelected(); + showProfile = buttonProfile.isSelected(); + + pauseSelection.setVisible(false); + pauseSelection.setMinValue(1); + pauseSelection.addListener(new ValueSelectionListener() { + @Override + public void onValueChanged(ValueSelection origin, double value, boolean editing) { + if (editing && (value >= 1) && (value <= imageBuffer.size())) { + updatePause(); + } + } + }); + imageBufferOverlay = new Text(renderer.getPenErrorText(), "", new Font("Verdana", Font.PLAIN, 12), new Point(-100, 20)); + imageBufferOverlay.setFixed(true); + imageBufferOverlay.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_RIGHT); + if (MainFrame.isDark()) { + textState.setEnabled(true); + } + } + + @Override + public void onStart() { + super.onStart(); + if (App.hasArgument("ct")) { + boolean direct = App.getArgumentValue("ct").equals("0") || App.getArgumentValue("ct").equalsIgnoreCase("false"); + buttonServer.setSelected(!direct); + buttonDirect.setSelected(direct); + } + } + + @Override + public void onStop() { + try { + if (camera != null) { + camera.close(); + camera = null; + server = null; + } + } catch (Exception ex) { + ex.printStackTrace(); + } + super.onStop(); + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + comboCameras.setEnabled(false); + if (App.hasArgument("s")) { + renderer.setDevice((Source) getDevice("image")); + renderer.setAutoScroll(true); + ((Source) getDevice("image")).addListener(new ImageListener() { + @Override + public void onImage(Object o, BufferedImage bi, Data data) { + manageFit(bi, data); + manageUserOverlays(bi, data); + } + + @Override + public void onError(Object o, Exception ex) { + } + } + ); + + } else { + usingServer = buttonServer.isSelected(); + updateCameraList(); + comboCameras.setEnabled(true); + setComboCameraSelection(-1); + + if (comboCameras.getModel().getSize() > 0) { + try { + if (App.hasArgument("cam")) { + setComboCameraSelection(App.getArgumentValue("cam")); + comboCamerasActionPerformed(null); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + startTimer(1000); + } + + DefaultComboBoxModel getCameraList(boolean fromServer) throws Exception { + DefaultComboBoxModel model = new DefaultComboBoxModel(); + if (fromServer) { + try (PipelineServer srv = newServer()) { + srv.initialize(); + List cameras = srv.getCameras(); + Collections.sort(cameras); + for (String camera : cameras) { + model.addElement(camera); + } + } + + } else { + ArrayList cameras = (ArrayList) getContext().getClassByName("SfCamera").getMethod("getCameras", new Class[]{}).invoke(null); + for (String cam : cameras) { + model.addElement(cam); + } + } + return model; + } + + PipelineServer newServer() throws IOException { + if (serverUrl != null) { + System.out.println("Connecting to server: " + serverUrl); + server = new PipelineServer(CAMERA_DEVICE_NAME, serverUrl); + } else { + System.out.println("Connecting to server"); + server = new PipelineServer(CAMERA_DEVICE_NAME); + } + return server; + } + + boolean updatingCameraSelection; + + void setComboCameraSelection(Object selection) { + updatingCameraSelection = true; + try { + comboCameras.setSelectedItem(selection); + } finally { + updatingCameraSelection = false; + } + } + + boolean usingServer; + + void updateCameraList() { + try { + String selected = (String) comboCameras.getSelectedItem(); + DefaultComboBoxModel model = getCameraList(usingServer); + if (App.hasArgument("cam")) { + String cam = App.getArgumentValue("cam"); + if (model.getIndexOf(cam) < 0) { + model.addElement(cam); + } + } + comboCameras.setModel(model); + if (selected != null) { + setComboCameraSelection(selected); + } + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + updateStop(); + } + } + + final Object lockOverlays = new Object(); + + void manageFit(BufferedImage bi, Data data) { + Overlay[][] fo = ((bi == null) || ((!showFit && !showProfile))) ? null : getFitOverlays(data); + synchronized (lockOverlays) { + fo = (fo == null) ? new Overlay[][]{null, null} : fo; + renderer.updateOverlays(fo[0], profileOv); + profileOv = fo[0]; + renderer.updateOverlays(fo[1], fitOv); + fitOv = fo[1]; + } + } + + void manageUserOverlays(BufferedImage bi, Data data) { + Overlay[] fo = (bi == null) ? null : getUserOverlays(data); + synchronized (lockOverlays) { + renderer.updateOverlays(fo, userOv); + userOv = fo; + } + } + + @Override + public void onStateChange(State state, State former) { + + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + //Callback to perform update - in event thread + @Override + protected void doUpdate() { + } + + Thread devicesInitTask; + + class ImageAverager extends SourceBase { + final ArrayList images = new ArrayList(); + ImageAverager() { + super("Image Averager"); + + camera.addListener(new ImageListener() { + @Override + public void onImage(Object o, BufferedImage bi, Data data) { + images.add(bi); + if (images.size()>10){ + images.remove(0); + } + BufferedImage ret = null; + for (BufferedImage ib : images){ + if (ret == null){ + ret = Utils.copy(ib, null, null); + } else { + Utils.add(ret, ib, true); + + } + } + //ret =Utils.scale(ret, 1.0/images.size()); + ImageAverager.this.pushImage(ret); + } + + @Override + public void onError(Object origin, Exception ex) { + } + }); + + } + } + + void setCamera(String cameraName) throws IOException, InterruptedException { + System.out.println("Initializing"); + parseUserOverlays(); + errorOverlay = null; + + if (dataTableDialog != null) { + dataTableDialog.dispose(); + dataTableDialog = null; + } + dataTableModel = null; + + if (calibrationDialolg != null) { + calibrationDialolg.dispose(); + calibrationDialolg = null; + } + + boolean was_server = false; + if (camera != null) { + //camera.removeAllListeners(); + was_server = (server != null); + camera.close(); + camera = null; + server = null; + } + instanceName = null; + renderer.setDevice(null); + + renderer.setShowReticle(false); + renderer.removeOverlays(fitOv); + renderer.removeOverlays(profileOv); + renderer.removeOverlays(userOv); + renderer.clear(); + renderer.resetZoom(); + + boolean changed = !String.valueOf(cameraName).equals(this.cameraName); + this.cameraName = cameraName; + + if (changed || buttonDirect.isSelected()) { + spinnerThreshold.setVisible(false); + spinnerGrThreshold.setVisible(false); + labelGrThreshold.setVisible(false); + spinnerGrScale.setVisible(false); + labelGrScale.setVisible(false); + checkThreshold.setEnabled(false); + checkGoodRegion.setEnabled(false); + } + if (changed) { + checkBackground.setEnabled(false); + if ((devicesInitTask != null) && (devicesInitTask.isAlive())) { + devicesInitTask.interrupt(); + } + if (screen != null) { + screen.close(); + screen = null; + } + if (filter != null) { + filter.close(); + filter = null; + } + } + if (cameraName == null) { + return; + } + + System.out.println("Setting camera: " + cameraName + " [" + (buttonServer.isSelected() ? "server" : "direct") + "]"); + + synchronized (imageBuffer) { + currentFrame = null; + imageBuffer.clear(); + } + + try { + if (buttonServer.isSelected()) { + camera = newServer(); + camera.getConfig().flipHorizontally = false; + camera.getConfig().flipVertically = false; + camera.getConfig().rotation = 0.0; + camera.getConfig().roiX = 0; + camera.getConfig().roiY = 0; + camera.getConfig().roiWidth = -1; + camera.getConfig().roiHeight = -1; + } else { + //camera = new SfCamera(CAMERA_DEVICE_NAME, cameraName); + camera = (ColormapSource) getContext().getClassByName("SfCamera").getConstructor(new Class[]{String.class, String.class}).newInstance(new Object[]{CAMERA_DEVICE_NAME, cameraName}); + } + camera.initialize(); + camera.assertInitialized(); + System.out.println("Camera initialization OK"); + if (server != null) { + //server.start(cameraName, false); + String pipelineName = cameraName + "_sp"; + instanceName = cameraName + "_sp1"; + if (!server.getPipelines().contains(pipelineName)) { + System.out.println("Creating pipeline: " + pipelineName); + HashMap config = new HashMap<>(); + config.put("camera_name", cameraName); + //server.createFromConfig(config, pipelineName); + server.savePipelineConfig(pipelineName, config); + } + server.start(pipelineName, instanceName); + + updateServerControls(); + checkThreshold.setEnabled(true); + checkGoodRegion.setEnabled(true); + } else { + checkThreshold.setSelected(false); + checkGoodRegion.setSelected(false); + if (polling <= 0) { + camera.setMonitored(true); + } else { + camera.setPolling(polling); + } + camera.setBackgroundEnabled(checkBackground.isSelected()); + } + + buttonReticle.setEnabled(camera.getConfig().isCalibrated()); + camera.getConfig().save(); + if (averaging) { + SourceBase source = new ImageAverager(); + renderer.setDevice(source); + } else { + renderer.setDevice(camera); + } + renderer.setAutoScroll(true); + renderer.setMarker(marker); + imageSize = null; + + camera.addListener(new ImageListener() { + @Override + public void onImage(Object o, BufferedImage bi, Data data) { + if (bi != null) { + if ((imageSize == null) || imageSize.width != bi.getWidth() || imageSize.height != bi.getHeight()) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if ((renderer.getMode() == RendererMode.Zoom) || (renderer.getMode() == RendererMode.Fixed)) { + centralizeRenderer(); + } + checkReticle(); + } + }); + imageSize = new Dimension(bi.getWidth(), bi.getHeight()); + } + renderer.setProfileSize(Math.min(bi.getWidth(), bi.getHeight())); + } + //renderer.setCalibration(camera.getCalibration()); + if (!renderer.isPaused()) { + if (data != null) { + synchronized (imageBuffer) { + currentFrame = new Frame(data); + if (imageBufferLenght >= 1) { + imageBuffer.add(currentFrame); + if (imageBuffer.size() > imageBufferLenght) { + imageBuffer.remove(0); + } + } + } + } + manageFit(bi, data); + manageUserOverlays(bi, data); + } + //updateImageData(); + } + + @Override + public void onError(Object o, Exception ex) { + //System.err.println(ex); + } + }); + + } catch (Exception ex) { + ex.printStackTrace(); + showException(ex); + renderer.clearOverlays(); + updateServerControls(); + if (renderer.getDevice() == null) { + //renderer.setZoom(1.0); + //renderer.setMode(RendererMode.Zoom); + errorOverlay = new Text(renderer.getPenErrorText(), ex.toString(), new Font("Verdana", Font.PLAIN, 12), new Point(20, 20)); + errorOverlay.setFixed(true); + errorOverlay.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_LEFT); + renderer.addOverlay(errorOverlay); + } + } finally { + //checkReticle(); + onTimer(); + } + onChangeColormap(null); + checkBackground.setEnabled(true); + if (changed) { + comboScreen.setModel(new DefaultComboBoxModel()); comboScreen.setEnabled(false); + comboFilter.setModel(new DefaultComboBoxModel()); comboFilter.setEnabled(false); + if (getCameraType(cameraName).equals("ELECTRONS")){ + //Parallelizing initialization + devicesInitTask = new Thread(() -> { + try { + if (cameraName.contains("DSRM")) { + screen = new DiscretePositioner("CurrentScreen", cameraName + ":POSITION_SP", cameraName + ":POSITION"); + } else { + screen = new DiscretePositioner("CurrentScreen", cameraName + ":SET_SCREEN1_POS", cameraName + ":GET_SCREEN1_POS"); + } + screen.setMonitored(true); + screen.initialize(); + DefaultComboBoxModel model = new DefaultComboBoxModel(); + for (String pos : screen.getPositions()) { + model.addElement(pos); + } + comboScreen.setModel(model); + comboScreen.setSelectedItem(screen.read()); + + } catch (Exception ex) { + comboScreen.setModel(new DefaultComboBoxModel()); + System.err.println(ex.getMessage()); + screen = null; + } + comboScreen.setEnabled(screen != null); + valueScreen.setDevice(screen); + + try { + filter = new DiscretePositioner("CurrentFilter", cameraName + ":SET_FILTER", cameraName + ":GET_FILTER"); + filter.setMonitored(true); + filter.initialize(); + DefaultComboBoxModel model = new DefaultComboBoxModel(); + for (String pos : filter.getPositions()) { + model.addElement(pos); + } + comboFilter.setModel(model); + comboFilter.setSelectedItem(filter.read()); + } catch (Exception ex) { + System.err.println(ex.getMessage()); + filter = null; + } + comboFilter.setEnabled(filter != null); + valueFilter.setDevice(filter); + }); + devicesInitTask.start(); + } + } + + } + + volatile Dimension imageSize; + + void checkReticle() { + if ((renderer.getDevice() != null) && (camera != null) && (camera.getConfig().isCalibrated()) && buttonReticle.isSelected()) { + //renderer.setCalibration(camera.getCalibration()); + renderer.configureReticle(new Dimension(800, 800), 200); + renderer.setShowReticle(true); + } else { + //renderer.setCalibration(null); + renderer.setShowReticle(false); + } + renderer.refresh(); + } + + void checkMarker() { + if (camera != null) { + if (buttonMarker.isSelected()) { + Dimension d = renderer.getImageSize(); + Point p = (d == null) ? new Point(renderer.getWidth() / 2, renderer.getHeight() / 2) : new Point(d.width / 2, d.height / 2); + Overlay ov = null; + marker = new Overlays.Crosshairs(renderer.getPenMarker(), p, new Dimension(100, 100)); + marker.setMovable(true); + marker.setPassive(false); + } else { + marker = null; + } + renderer.setMarker(marker); + } + } + + void updateZoom() { + try { + buttonZoomStretch.setSelected(renderer.getMode() == RendererMode.Stretch); + buttonZoomFit.setSelected(renderer.getMode() == RendererMode.Fit); + if (renderer.getMode() == RendererMode.Fixed) { + buttonZoomNormal.setSelected(true); + } else if (renderer.getMode() == RendererMode.Zoom) { + if (renderer.getZoom() == 1) { + buttonZoomNormal.setSelected(true); + } else if (renderer.getZoom() == 0.5) { + buttonZoom05.setSelected(true); + } else if (renderer.getZoom() == 0.25) { + buttonZoom025.setSelected(true); + } else if (renderer.getZoom() == 2.0) { + buttonZoom2.setSelected(true); + } else { + buttonGroup1.clearSelection(); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + boolean updatingColormap; + + void updateColormap() { + updatingColormap = true; + try { + if ((camera != null) && (camera instanceof ColormapSource)) { + ColormapSourceConfig config = ((ColormapSource) camera).getConfig(); + comboColormap.setSelectedItem(config.colormap); + if (config.isDefaultColormap()) { + buttonFullRange.setSelected(true); + } else if (config.colormapAutomatic) { + buttonAutomatic.setSelected(true); + } else { + buttonManual.setSelected(true); + } + btFixColormapRange.setVisible(buttonAutomatic.isSelected()); + + //spinnerMin.setVisible(buttonManual.isSelected()); labelMin.setVisible(spinnerMin.isVisible()); + //spinnerMax.setVisible(buttonManual.isSelected()); labelMax.setVisible(spinnerMax.isVisible()); + spinnerMin.setEnabled(buttonManual.isSelected()); + spinnerMax.setEnabled(buttonManual.isSelected()); + //spinnerMin.setValue(Double.isNaN(config.colormapMin) ? 0 : Math.min(Math.max((int) config.colormapMin, 0), 65535)); + //spinnerMax.setValue(Double.isNaN(config.colormapMax) ? 0 : Math.min(Math.max((int) config.colormapMax, 0), 65535)); + if (!Double.isNaN(config.colormapMin)) { + spinnerMin.setValue(Math.min(Math.max((int) config.colormapMin, 0), 65535)); + } + if (!Double.isNaN(config.colormapMax)) { + spinnerMax.setValue(Math.min(Math.max((int) config.colormapMax, 0), 65535)); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + updatingColormap = false; + } + + boolean updatingServerControls; + + void updateServerControls() { + if (server != null) { + updatingServerControls = true; + try { + checkBackground.setSelected(server.getBackgroundSubtraction()); + Double threshold = (server.getThreshold()); + checkThreshold.setSelected(threshold != null); + spinnerThreshold.setValue((threshold == null) ? 0 : threshold); + Map gr = (server.getGoodRegion()); + checkGoodRegion.setSelected(gr != null); + if (gr != null) { + spinnerGrThreshold.setValue(((Number) gr.get("threshold")).doubleValue()); + spinnerGrScale.setValue(((Number) gr.get("gfscale")).doubleValue()); + } + } catch (Exception ex) { + } + goodRegion = checkGoodRegion.isSelected(); + spinnerThreshold.setVisible(checkThreshold.isSelected()); + spinnerGrThreshold.setVisible(goodRegion); + labelGrThreshold.setVisible(spinnerGrThreshold.isVisible()); + spinnerGrScale.setVisible(goodRegion); + labelGrScale.setVisible(spinnerGrScale.isVisible()); + updatingServerControls = false; + } + } + + boolean isCameraStopped() { + if (server != null) { + if (!server.isStarted()) { + return true; + } + } + return ((camera == null) || (camera.isClosed()) || !buttonStop.isEnabled()); + } + + void updateStop() { + buttonStop.setEnabled(comboCameras.getSelectedItem() != null); + buttonStop.setText(isCameraStopped() ? "Start" : "Stop"); + + } + + @Override + protected void onTimer() { + for (Device dev : new Device[]{screen, filter}) { + if (dev != null) { + dev.request(); + } + } + + textState.setText((camera == null) ? "" : camera.getState().toString()); + buttonArgs.setEnabled(camera != null); + if (App.hasArgument("s")) { + try { + ((Source) getDevice("image")).initialize(); + } catch (IOException ex) { + Logger.getLogger(ScreenPanel1.class.getName()).log(Level.SEVERE, null, ex); + } catch (InterruptedException ex) { + Logger.getLogger(ScreenPanel1.class.getName()).log(Level.SEVERE, null, ex); + } + } + if (renderer.isPaused() != buttonPause.isSelected()) { + buttonPause.setSelected(renderer.isPaused()); + buttonPauseActionPerformed(null); + } + if (renderer.getShowReticle() != buttonReticle.isSelected()) { + //buttonReticle.setSelected(renderer.getShowReticle()); + } + if ((renderer.getMarker() == null) && buttonMarker.isSelected()) { + buttonMarker.setSelected(false); + } + if (!renderer.isPaused() && (dataTableDialog != null) && (dataTableDialog.isShowing())) { + updateStreamData(); + } + updateZoom(); + updateColormap(); + updateStop(); + buttonSave.setSelected(renderer.isSnapshotDialogVisible()); + checkHistogram.setSelected((histogramDialog != null) && (histogramDialog.isShowing())); + } + + Pen penFit = new Pen(new Color(192, 105, 0), 0); + Pen penCross = new Pen(new Color(192, 105, 0), 0); + + public Frame getCurrentFrame() { + if ((imageBufferLenght > 1) && (renderer.isPaused())) { + int index = ((int) pauseSelection.getValue()) - 1; + synchronized (imageBuffer) { + return (index < imageBuffer.size()) ? imageBuffer.get(index) : null; + } + } + return currentFrame; + } + + Frame getFrame(Data data) { + synchronized (imageBuffer) { + for (Frame f : imageBuffer) { + if (f.data == data) { + return f; + } + } + } + return null; + } + + Overlay[][] getFitOverlays(Data data) { + Overlays.Polyline hgaussian = null; + Overlays.Polyline vgaussian = null; + Overlays.Polyline hprofile = null; + Overlays.Polyline vprofile = null; + Double xMean = null, xSigma = null, xNorm = null, xCom = null, xRms = null; + Double yMean = null, ySigma = null, yNorm = null, yCom = null, yRms = null; + double[] pX = null, pY = null, gX = null, gY = null; + int height = data.getHeight(); + int width = data.getWidth(); + //Double xCom=null, yCom=null; + if (data != null) { + int profileSize = renderer.getProfileSize(); + if ((useServerStats) && (server != null)) { + try { + + ImageData id = getFrame(data); + if (id == null) { + return null; + } + xMean = id.x_fit_mean; + xSigma = id.x_fit_standard_deviation; + yMean = id.y_fit_mean; + ySigma = id.y_fit_standard_deviation; + gX = id.x_fit_gauss_function; + gY = id.y_fit_gauss_function; + pX = id.x_profile; + pY = id.y_profile; + xCom = id.x_center_of_mass; + xRms = id.x_rms; + yCom = id.y_center_of_mass; + yRms = id.y_rms; + + profileSize /= 4; + if (pX != null) { + int[] x = Arr.indexesInt(pX.length); + int[] y = new int[pX.length]; + int[] p = new int[pX.length]; + List l = Arrays.asList((Double[]) Convert.toWrapperArray(pX)); + Double min = Collections.min(l); + Double max = Collections.max(l); + double minPlot = min; + double rangePlot = max - min; + + for (int i = 0; i < x.length; i++) { + if (gX != null) { + y[i] = (int) (height - 1 - (((gX[i] - minPlot) / rangePlot) * profileSize)); + } + p[i] = (int) (height - 1 - (((pX[i] - minPlot) / rangePlot) * profileSize)); + } + + if (goodRegion) { + for (int i = 0; i < x.length; i++) { + y[i] = (Double.isNaN(gX[i])) ? 100000 : y[i]; + } + } + + vgaussian = new Overlays.Polyline(penFit, x, y); + vprofile = new Overlays.Polyline(renderer.getPenProfile(), x, p); + } + + if (pY != null) { + int[] y = Arr.indexesInt(pY.length); + int[] x = new int[pY.length]; + int[] p = new int[pY.length]; + + List l = Arrays.asList((Double[]) Convert.toWrapperArray(pY)); + Double min = Collections.min(l); + Double max = Collections.max(l); + double minPlot = min; + double rangePlot = max - min; + + for (int i = 0; i < x.length; i++) { + if (gY != null) { + x[i] = (int) (((gY[i] - minPlot) / rangePlot) * profileSize); + } + p[i] = (int) (((pY[i] - minPlot) / rangePlot) * profileSize); + } + + if (goodRegion) { + for (int i = 0; i < x.length; i++) { + x[i] = (Double.isNaN(gY[i])) ? -1 : x[i]; + } + } + hgaussian = new Overlays.Polyline(penFit, x, y); + hprofile = new Overlays.Polyline(renderer.getPenProfile(), p, y); + } + } catch (Exception ex) { + System.err.println(ex.getMessage()); + return null; + } + } else { + ArrayProperties properties = data.getProperties(); + double maxPlot = properties.max; + double minPlot = properties.min; + double rangePlot = maxPlot - minPlot; + + if (rangePlot <= 0) { + return null; + } + if (renderer.getCalibration() != null) { + try { + double[] sum = data.integrateVertically(true); + double[] saux = new double[sum.length]; + int[] p = new int[sum.length]; + double[] x_egu = renderer.getCalibration().getAxisX(sum.length); + double[] comRms = getComRms(sum, x_egu); + xCom = comRms[0]; + xRms = comRms[1]; + int[] x = Arr.indexesInt(sum.length); + DescriptiveStatistics stats = new DescriptiveStatistics(sum); + double min = stats.getMin(); + for (int i = 0; i < sum.length; i++) { + saux[i] = sum[i] - min; + } + if (showFit) { + double[] gaussian = fitGaussian(saux, x); + if (gaussian != null) { + if ((gaussian[2] < sum.length * 0.45) + && (gaussian[2] > 2) + && (gaussian[0] > min * 0.03)) { + xNorm = gaussian[0]; + xMean = gaussian[1]; + xSigma = gaussian[2]; + double[] fit = getFitFunction(gaussian, x); + int[] y = new int[x.length]; + for (int i = 0; i < x.length; i++) { + y[i] = (int) (height - 1 - ((((fit[i] + min) / height - minPlot) / rangePlot) * profileSize)); + } + vgaussian = new Overlays.Polyline(penFit, x, y); + } + } + } + if (showProfile) { + for (int i = 0; i < x.length; i++) { + p[i] = (int) (height - 1 - (((sum[i] / height - minPlot) / rangePlot) * profileSize)); + } + vprofile = new Overlays.Polyline(renderer.getPenProfile(), x, p); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + + try { + double[] sum = data.integrateHorizontally(true); + double[] saux = new double[sum.length]; + int[] p = new int[sum.length]; + double[] y_egu = renderer.getCalibration().getAxisY(sum.length); + double[] comRms = getComRms(sum, y_egu); + yCom = comRms[0]; + yRms = comRms[1]; + int[] x = Arr.indexesInt(sum.length); + DescriptiveStatistics stats = new DescriptiveStatistics(sum); + double min = stats.getMin(); + for (int i = 0; i < sum.length; i++) { + saux[i] = sum[i] - min; + } + + if (showFit) { + double[] gaussian = fitGaussian(saux, x); + if (gaussian != null) { + //Only aknowledge beam fully inside the image and peak over 3% of min + if ((gaussian[2] < sum.length * 0.45) + && (gaussian[2] > 2) + && (gaussian[0] > min * 0.03)) { + yNorm = gaussian[0]; + yMean = gaussian[1]; + ySigma = gaussian[2]; + double[] fit = getFitFunction(gaussian, x); + + int[] y = new int[x.length]; + for (int i = 0; i < x.length; i++) { + y[i] = (int) ((((fit[i] + min) / width - minPlot) / rangePlot) * profileSize); + } + hgaussian = new Overlays.Polyline(penFit, y, x); + } + } + } + if (showProfile) { + for (int i = 0; i < x.length; i++) { + p[i] = (int) (((sum[i] / width - minPlot) / rangePlot) * profileSize); + } + hprofile = new Overlays.Polyline(renderer.getPenProfile(), p, x); + } + + } catch (Exception ex) { + ex.printStackTrace(); + } + if (xSigma != null) { + xSigma *= renderer.getCalibration().getScaleX(); + } + if (ySigma != null) { + ySigma *= renderer.getCalibration().getScaleY(); + } + if (xMean != null) { + xMean = data.getX((int) Math.round(xMean)); + } + if (yMean != null) { + yMean = data.getY((int) Math.round(yMean)); + } + } + } + final String units = (renderer.getCalibration() != null) ? "\u00B5m" : "px"; + final String fmt = "%7.1f" + units; + Overlays.Text textCom = null; + Overlay[] pOv = null, fOv = null; + Point textPosition = new Point(12, 20); + if (showProfile) { + if ((xCom != null) && (yCom != null)) { + String text = String.format("com x: m=" + fmt + " \u03C3=" + fmt + "\ncom y: m=" + fmt + " \u03C3=" + fmt, xCom, xRms, yCom, yRms); + textCom = new Overlays.Text(renderer.getPenProfile(), text, new Font(Font.MONOSPACED, 0, 14), textPosition); + textCom.setFixed(true); + textCom.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_LEFT); + } + pOv = new Overlay[]{hprofile, vprofile, textCom}; + } + if (showFit) { + Overlays.Crosshairs cross = null; + Overlays.Text textFit = null; + if ((xMean != null) && (yMean != null)) { + String text = String.format("fit x: m=" + fmt + " \u03C3=" + fmt + "\nfit y: m=" + fmt + " \u03C3=" + fmt, xMean, xSigma, yMean, ySigma); + textFit = new Overlays.Text(penFit, text, new Font(Font.MONOSPACED, 0, 14), showProfile ? new Point(12, 54) : textPosition); + textFit.setFixed(true); + textFit.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_LEFT); + Point center = new Point(xMean.intValue(), yMean.intValue()); + if (renderer.getCalibration() != null) { + center = renderer.getCalibration().convertToImagePosition(new PointDouble(xMean, yMean)); + xSigma /= renderer.getCalibration().getScaleX(); + ySigma /= renderer.getCalibration().getScaleY(); + } + cross = new Overlays.Crosshairs(penCross, center, new Dimension(Math.abs(2 * xSigma.intValue()), 2 * Math.abs(ySigma.intValue()))); + } + fOv = new Overlay[]{hgaussian, vgaussian, cross, textFit}; + + if (goodRegion) { + try { + double[] x = getServerDoubleArray("gr_x_axis"); + double[] y = getServerDoubleArray("gr_y_axis"); + double x1 = x[0]; + double x2 = x[x.length - 1]; + double y1 = y[0]; + double y2 = y[y.length - 1]; + Overlays.Rect goodRegionOv = new Overlays.Rect(new Pen(penFit.getColor(), 0, Pen.LineStyle.dotted)); + goodRegionOv.setCalibration(renderer.getCalibration()); + goodRegionOv.setAbsolutePosition(new PointDouble(x1, y1)); + goodRegionOv.setAbsoluteSize(new DimensionDouble(x2 - x1, y2 - y1)); + fOv = Arr.append(fOv, goodRegionOv); + } catch (Exception ex) { + } + } + + } + return new Overlay[][]{pOv, fOv}; + } + return null; + } + + class UserOverlay { + + String name; + Overlay obj; + String[] channels; + } + ArrayList userOverlayConfig; + + void parseUserOverlays() { + Properties userOverlays = new Properties(); + userOverlayConfig = new ArrayList<>(); + if (userOverlaysConfigFile != null) { + try { + try (FileInputStream in = new FileInputStream(getContext().getSetup().expandPath(userOverlaysConfigFile))) { + userOverlays.load(in); + + for (String name : userOverlays.stringPropertyNames()) { + String val = userOverlays.getProperty(name); + try { + UserOverlay uo = new UserOverlay(); + uo.name = name; + String type = val.substring(0, val.indexOf("(")).trim(); + String pars = val.substring(val.indexOf("(") + 1, val.lastIndexOf(")")).trim(); + String[] tokens = pars.split(","); + for (int i = 0; i < tokens.length; i++) { + tokens[i] = tokens[i].trim(); + } + Color color = Color.GRAY; + try { + color = (Color) Color.class.getField(tokens[tokens.length - 1].toUpperCase()).get(null); + } catch (Exception ex) { + } + Pen pen = new Pen(color); + try { + String[] penTokens = tokens[tokens.length - 1].split(":"); + color = (Color) Color.class.getField(penTokens[0].toUpperCase()).get(null); + int width = Integer.valueOf(penTokens[1]); + Pen.LineStyle style = Pen.LineStyle.valueOf(penTokens[2]); + pen = new Pen(color, width, style); + } catch (Exception ex) { + } + switch (type) { + case "Point": + uo.obj = new Overlays.Crosshairs(); + uo.obj.setSize(new Dimension(Integer.valueOf(tokens[2]), Integer.valueOf(tokens[3]))); + break; + case "Line": + uo.obj = new Overlays.Line(); + break; + case "Arrow": + uo.obj = new Overlays.Arrow(); + break; + case "Rect": + uo.obj = new Overlays.Rect(); + break; + case "Ellipse": + uo.obj = new Overlays.Ellipse(); + break; + case "Polyline": + uo.obj = new Overlays.Polyline(); + break; + } + if (type.equals("Polyline") || type.equals("Point")) { + uo.channels = new String[]{tokens[0], tokens[1]}; + } else { + uo.channels = new String[]{tokens[0], tokens[1], tokens[2], tokens[3]}; + } + uo.obj.setPen(pen); + userOverlayConfig.add(uo); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + Overlay[] getUserOverlays(Data data) { + ArrayList ret = new ArrayList<>(); + if (server != null) { + for (UserOverlay uo : userOverlayConfig) { + try { + Overlay ov = uo.obj; + //Overlay ov = (Overlay)uo.cls.newInstance(); + ov.setCalibration(renderer.getCalibration()); + boolean valid = false; + if (ov instanceof Overlays.Polyline) { + double[] x = (uo.channels[0].equals("null")) ? null : getServerDoubleArray(uo.channels[0]); + double[] y = (uo.channels[1].equals("null")) ? null : getServerDoubleArray(uo.channels[1]); + if ((x != null) || (y != null)) { + if (x == null) { + x = (renderer.getCalibration() == null) ? Arr.indexesDouble(y.length) : renderer.getCalibration().getAxisX(y.length); + } + if (y == null) { + y = (renderer.getCalibration() == null) ? Arr.indexesDouble(x.length) : renderer.getCalibration().getAxisY(x.length); + } + ((Overlays.Polyline) ov).updateAbsolute(x, y); + valid = true; + } + } else { + Double x = getServerDouble(uo.channels[0]); + Double y = getServerDouble(uo.channels[1]); + if ((x != null) && (y != null)) { + PointDouble position = new PointDouble(x, y); + ov.setAbsolutePosition(position); + if (!(ov instanceof Overlays.Crosshairs)) { + Double x2 = getServerDouble(uo.channels[2]); + Double y2 = getServerDouble(uo.channels[3]); + if ((x != null) && (y != null)) { + DimensionDouble size = new DimensionDouble(x2 - position.x, y2 - position.y); + ov.setAbsoluteSize(size); + valid = true; + } + } else { + valid = true; + } + } + } + if (valid) { + ret.add(ov); + } + } catch (Exception ex) { + //ex.printStackTrace(); + } + } + } + return ret.toArray(new Overlay[0]); + } + + double[] getComRms(double[] arr, double[] x) { + if (arr != null) { + double xmd = 0; + double xmd2 = 0; + double total = 0; + for (int i = 0; i < arr.length; i++) { + double v = (arr[i] * x[i]); + xmd += v; + xmd2 += (v * x[i]); + total += arr[i]; + } + if (total > 0) { + double com = xmd / total; + double com2 = xmd2 / total; + double rms = Math.sqrt(Math.abs(com2 - com * com)); + return new double[]{com, rms}; + } + } + return new double[]{Double.NaN, Double.NaN}; + } + + double[] fitGaussianScript(int[] y, int[] x) { + ScriptManager sm = Context.getInstance().getScriptManager(); + ArrayProperties pY = ArrayProperties.get(y); + sm.setVar("y", y); + sm.setVar("x", x); + InterpreterResult r = sm.eval("r = fit_gaussians(y, x, [" + pY.maxIndex + ",])"); + if (r.exception != null) { + r.exception.printStackTrace(); + } else { + List ret = (List) sm.getVar("r"); + if ((ret != null) && (ret.size() == 1) && (ret.get(0) instanceof List) && (((List) (ret.get(0))).size() == 3)) { + double norm = (Double) ((List) ret.get(0)).get(0); + double mean = (Double) ((List) ret.get(0)).get(1); + double sigma = (Double) ((List) ret.get(0)).get(2); + return new double[]{norm, mean, sigma}; + } + } + return null; + } + + double[] fitGaussian(double[] y, int[] x) { + try { + ArrayProperties pY = ArrayProperties.get(y); + GaussianCurveFitter fitter = GaussianCurveFitter.create().withStartPoint(new double[]{(pY.max - pY.min) / 2, x[pY.maxIndex], 1.0}).withMaxIterations(1000); + ArrayList values = new ArrayList<>(); + for (int i = 0; i < y.length; i++) { + values.add(new WeightedObservedPoint(1.0, x[i], y[i])); + } + return fitter.fit(values); + } catch (Exception ex) { + return null; + } + + } + + double[] getFitFunction(double[] pars, int[] x) { + double[] fit = new double[x.length]; + Gaussian g = new Gaussian(pars[0], pars[1], pars[2]); + for (int i = 0; i < x.length; i++) { + fit[i] = g.value(x[i]); + } + return fit; + } + + void setHistogramVisible(boolean value) { + if (value) { + if ((histogramDialog == null) || (!histogramDialog.isShowing())) { + Histogram histogram = new Histogram(true); + histogram.setRenderer(renderer); + histogramDialog = SwingUtils.showDialog(SwingUtils.getWindow(renderer), "Histogram", null, histogram); + renderer.refresh(); + } + } else { + if (histogramDialog != null) { + histogramDialog.setVisible(false); + histogramDialog = null; + } + } + } + + void setLaserState(boolean value) throws Exception { + System.out.println("Setting laser state: " + value); + Epics.putq("SIN-TIMAST-TMA:Beam-Las-Delay-Sel", value ? 0 : 1); + Epics.putq("SIN-TIMAST-TMA:Beam-Apply-Cmd.PROC", 1); + Thread.sleep(3000); + } + + boolean getLaserState() throws Exception { + return (Epics.get("SIN-TIMAST-TMA:Beam-Las-Delay-Sel", Integer.class) == 0); + } + + void elog(String logbook, String title, String message, String[] attachments) throws Exception { + String domain = ""; + String category = "Info"; + String entry = ""; + StringBuffer cmd = new StringBuffer(); + + cmd.append("G_CS_ELOG_add -l \"").append(logbook).append("\" "); + cmd.append("-a \"Author=ScreenPanel\" "); + cmd.append("-a \"Type=pshell\" "); + cmd.append("-a \"Entry=").append(entry).append("\" "); + cmd.append("-a \"Title=").append(title).append("\" "); + cmd.append("-a \"Category=").append(category).append("\" "); + cmd.append("-a \"Domain=").append(domain).append("\" "); + for (String attachment : attachments) { + cmd.append("-f \"").append(attachment).append("\" "); + } + cmd.append("-n 1 "); + cmd.append("\"").append(message).append("\" "); + System.out.println(cmd.toString()); + + final Process process = Runtime.getRuntime().exec(new String[]{"bash", "-c", cmd.toString()}); + new Thread(() -> { + try { + process.waitFor(); + int bytes = process.getInputStream().available(); + byte[] arr = new byte[bytes]; + process.getInputStream().read(arr, 0, bytes); + System.out.println(new String(arr)); + bytes = process.getErrorStream().available(); + arr = new byte[bytes]; + process.getErrorStream().read(arr, 0, bytes); + System.err.println(new String(arr)); + } catch (Exception ex) { + System.err.println(ex); + } + }).start(); + } + + void centralizeRenderer() { + Point center = null; + Dimension size = renderer.getImageSize(); + double zoom = (renderer.getMode() == RendererMode.Fixed) ? 1.0 : renderer.getZoom(); + if (renderer.getCalibration() != null) { + center = renderer.getCalibration().getCenter(); + } else if (size != null) { + center = new Point(size.width / 2, size.height / 2); + } + if (center != null) { + Point topleft = new Point(Math.max((int) (center.x - renderer.getWidth() / 2 / zoom), 0), + Math.max((int) (center.y - renderer.getHeight() / 2 / zoom), 0)); + renderer.setViewPosition(topleft); + } + } + + void updatePause() { + int index = ((int) pauseSelection.getValue()) - 1; + synchronized (imageBuffer) { + if (index < imageBuffer.size()) { + Data data = imageBuffer.get(index).data; + BufferedImage image = camera.generateImage(data); + renderer.setImage(renderer.getOrigin(), image, data); + imageBufferOverlay.update(Chrono.getTimeStr(data.getTimestamp(), "HH:mm:ss.SSS")); + manageFit(image, data); + manageUserOverlays(image, data); + } + } + } + + /* + void writeFrameMetadata(String path, Frame frame) throws Exception { + getContext().getDataManager().setAttribute("/", "Camera", String.valueOf(cameraName)); + getContext().getDataManager().setAttribute("/", "Screen", String.valueOf(valueScreen.getLabel().getText())); + getContext().getDataManager().setAttribute("/", "Filter", String.valueOf(valueFilter.getLabel().getText())); + Calibration cal = renderer.getCalibration(); + getContext().getDataManager().setAttribute("/", "Calibration", cal == null ? new double[]{1, 1, 0, 0} + : new double[]{cal.getScaleX(), cal.getScaleY(), cal.getOffsetX(), cal.getOffsetY()}); + getContext().getDataManager().setAttribute(path, "Timestamp", Chrono.getTimeStr(frame.data.getTimestamp(), "HH:mm:ss.SSS")); + if (server != null) { + try { + getContext().getDataManager().setAttribute("/", "ROI", server.getRoi()); + } catch (Exception ex) { + getContext().getDataManager().setAttribute("/", "ROI", new int[]{0, 0, -1, -1}); + } + if (frame != null) { + for (Field f : ImageData.class.getFields()) { + Object value = f.get(frame); + getContext().getDataManager().setAttribute(path, f.getName(), (value == null) ? Double.NaN : value); + } + } + for (String name : new String[]{"x_axis", "y_axis", "gr_x_axis", "gr_y_axis"}) { + double[] val = getServerDoubleArray(name); + getContext().getDataManager().setAttribute("/", "GoodRegion", goodRegion); + if (val != null) { + getContext().getDataManager().setAttribute("/", name, val); + } + } + } + } + */ + + void saveSnapshot() throws Exception { + /* + getContext().setExecutionPars("snapshot"); + String path = "/data"; + String snapshotFile = null; + synchronized (imageBuffer) { + Frame id = getCurrentFrame(); + if (id == null) { + throw new Exception("No current image"); + } + Object data = id.data.getMatrix(); + getContext().getDataManager().setDataset(path, data, id.data.isUnsigned()); + writeFrameMetadata(path, id); + getContext().getDataManager().closeOutput(); + //Enforce the same timestamp to data & image files. + //snapshotFile = getContext().getSetup().expandPath("{images}/{date}_{time}_snapshot.png", getContext().getExecutionPars().getStart()); + snapshotFile = getContext().getExecutionPars().getPath() + ".png"; + //renderer.saveSnapshot(snapshotFile, "png", true); + ImageBuffer.saveImage(SwingUtils.createImage(renderer), snapshotFile, "png"); + } + */ + String snapshotFile = null; + synchronized (imageBuffer) { + Frame frame = getCurrentFrame(); + if (frame == null) { + throw new Exception("No current image"); + } + ArrayList frames = new ArrayList<>(); + frames.add(frame); + this.saveFrames("camera_snapshot", frames); + + //Enforce the same timestamp to data & image files. + //snapshotFile = getContext().getSetup().expandPath("{images}/{date}_{time}_snapshot.png", getContext().getExecutionPars().getStart()); + snapshotFile = getContext().getExecutionPars().getPath() + ".png"; + //renderer.saveSnapshot(snapshotFile, "png", true); + ImageBuffer.saveImage(SwingUtils.createImage(renderer), snapshotFile, "png"); + } + + JPanel panel = new JPanel(); + GridBagLayout layout = new GridBagLayout(); + layout.columnWidths = new int[]{0, 180}; //Minimum width + layout.rowHeights = new int[]{30, 30, 30}; //Minimum height + panel.setLayout(layout); + JComboBox comboLogbook = new JComboBox(new String[]{"SwissFEL commissioning data", "SwissFEL commissioning"}); + JTextField textComment = new JTextField(); + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + panel.add(new JLabel("Data file:"), c); + c.gridy = 1; + panel.add(new JLabel("Logbook:"), c); + c.gridy = 2; + panel.add(new JLabel("Comment:"), c); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + panel.add(textComment, c); + c.gridy = 1; + panel.add(comboLogbook, c); + c.gridy = 0; + panel.add(new JLabel(getContext().getExecutionPars().getPath()), c); + + if (SwingUtils.showOption(getTopLevel(), "Success", panel, OptionType.OkCancel) == OptionResult.Yes) { + StringBuilder message = new StringBuilder(); + message.append("Camera: ").append(cameraName).append(" ("). + append((server != null) ? "server" : "direct").append(")").append("\n"); + message.append("Screen: ").append(String.valueOf(valueScreen.getLabel().getText())).append("\n"); + message.append("Filter: ").append(String.valueOf(valueFilter.getLabel().getText())).append("\n"); + message.append("Data file: ").append(getContext().getExecutionPars().getPath()).append("\n"); + message.append("Comment: ").append(textComment.getText()).append("\n"); + if ((fitOv != null) && (fitOv.length > 5)) { + Overlays.Text text = (Overlays.Text) fitOv[5]; + message.append(text.getText()).append("\n"); + } + elog((String) comboLogbook.getSelectedItem(), "ScreenPanel Snapshot", message.toString(), new String[]{snapshotFile}); + } + //SwingUtils.showMessage(getTopLevel(), "Success", "Generated data file:\n" + getContext().getExecutionPars().getPath(), 5000); + //elog("SwissFEL commissioning data", "ScreenPanel Snapshot", message.toString(), new String[]{snapshotFile}); + } + + + + public static String getCameraType(String name){ + for (String s : new String[]{"LCAM"}){ + if (name.contains(s)){ + return "LCAM"; + } + } + for (String s : new String[]{"DSCR", "DSRM", "DLAC"}){ + if (name.contains(s)){ + return "ELECTRONS"; + } + } + for (String s : new String[]{"PROF", "PPRM", "PSSS", "PSCR", "PSRD"}){ + if (name.contains(s)){ + return "PHOTONICS"; + } + } + return "UNKNOWN"; + } + + public Map getProcessingParameters(StreamValue value) throws IOException { + return (Map) JsonSerializer.decode(value.getValue("processing_parameters").toString(), Map.class); + } + + void saveFrames(String name, ArrayList frames) throws IOException{ + ArrayList values = new ArrayList<>(); + for (Frame frame : frames){ + values.add(frame.cache); + } + saveImages(name, values); + } + + void saveImages(String name, ArrayList images) throws IOException{ + int depth = images.size(); + if (depth == 0){ + return; + } + StreamValue first = images.get(0); + String pathRoot = "/camera1/"; + String pathImage = pathRoot + "image"; + String pathPid = pathRoot + "pulse_id"; + String pathTimestampStr = pathRoot + "timestamp_str"; + Map processingPars = getProcessingParameters(first); + System.out.println(processingPars); + String camera = (String) processingPars.get("camera_name"); + String type = getCameraType(camera); + + int width = ((Number)first.getValue("width")).intValue(); + int height = ((Number)first.getValue("height")).intValue(); + Class dataType = first.getValue("image").getClass().getComponentType(); + + getContext().setExecutionPars(name); + DataManager dm = getContext().getDataManager(); + + //Create tables + dm.createDataset(pathImage, dataType, new int[]{depth, height, width}); + dm.createDataset(pathPid, Long.class, new int[]{depth}); + dm.createDataset(pathTimestampStr, String.class, new int[]{depth}); + for (String id : first.getIdentifiers()){ + Object val = first.getValue(id); + if (id.equals("image")){ + } else if (id.equals("processing_parameters")){ + Map pars = getProcessingParameters(first); + for (String key : pars.keySet()){ + if ((pars.get(key) != null) && (pars.get(key) instanceof Map)){ + for (Object k : ((Map)pars.get(key)).keySet()){ + Object v = ((Map)pars.get(key)).get(k); + dm.setAttribute(pathImage, key + " " + k, (v == null)? "" : v); + } + } else { + dm.setAttribute(pathImage, key, (pars.get(key) == null)? "" : pars.get(key)); + } + } + } else if (val.getClass().isArray()) { + dm.createDataset(pathRoot + id, Double.class, new int[]{depth, Array.getLength(val)}); + } else { + dm.createDataset(pathRoot + id, val.getClass(), new int[]{depth}); + } + } + + //Add metadata + dm.setAttribute(pathRoot,"Camera", camera); + dm.setAttribute(pathRoot,"Images", depth); + dm.setAttribute(pathRoot,"Interval", -1); + dm.setAttribute(pathRoot,"Type", type); + if (type.equals("ELECTRONS")){ + dm.setAttribute(pathRoot,"Screen", String.valueOf(valueScreen.getLabel().getText())); + dm.setAttribute(pathRoot,"Filter", String.valueOf(valueFilter.getLabel().getText())); + } + + //Save data + for (int index=0; index x = new ArrayList<>(); + ArrayList y = new ArrayList<>(); + synchronized (imageBuffer) { + for (int i = 0; i < imageBuffer.size(); i++) { + Frame frame = imageBuffer.get(i); + String path = "/data_" + i; + getContext().getDataManager().setDataset(path, frame.data.getMatrix(), frame.data.isUnsigned()); + writeFrameMetadata(path, frame); + x.add(frame.x_fit_mean); + y.add(frame.y_fit_mean); + } + } + DescStatsDouble xs = new DescStatsDouble(x.toArray(new Double[0]), -1); + DescStatsDouble ys = new DescStatsDouble(y.toArray(new Double[0]), -1); + + getContext().getDataManager().closeOutput(); + ) + */ + synchronized (imageBuffer) { + saveFrames("camera_stack", imageBuffer); + } + SwingUtils.showMessage(getTopLevel(), "Success", "Generated data file:\n" + getContext().getExecutionPars().getPath(), 5000); + } + + StandardDialog calibrationDialolg; + + void calibrate() throws Exception { + if (server != null) { + server.resetRoi(); + calibrationDialolg = (StandardDialog) getContext().getClassByName("CameraCalibrationDialog").getConstructors()[0].newInstance(new Object[]{getTopLevel(), server.getCurrentCamera(), renderer}); + SwingUtils.centerComponent(getTopLevel(), calibrationDialolg); + calibrationDialolg.setVisible(true); + calibrationDialolg.setListener(new StandardDialogListener() { + @Override + public void onWindowOpened(StandardDialog dlg) { + } + + @Override + public void onWindowClosed(StandardDialog dlg, boolean accepted) { + if (accepted) { + //comboCamerasActionPerformed(null); + } + } + }); + } + } + + StandardDialog dataTableDialog; + DefaultTableModel dataTableModel; + + void showStreamData() { + dataTableModel = null; + if (server != null) { + + if ((dataTableDialog != null) && (dataTableDialog.isShowing())) { + SwingUtils.centerComponent(getTopLevel(), dataTableDialog); + dataTableDialog.requestFocus(); + return; + } + //String msg = String.join("\n", ids); + //SwingUtils.showMessage(getTopLevel(), "Image Data", msg); + dataTableModel = new DefaultTableModel(new Object[0][2], new String[]{"Name", "Value"}) { + public Class getColumnClass(int columnIndex) { + return String.class; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + }; + updateStreamData(); + StreamValue val = server.getStream().take(); + JTable dataTable = new JTable(dataTableModel); + dataTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + dataTable.setCellSelectionEnabled(true); + dataTable.getTableHeader().setReorderingAllowed(false); + dataTable.getTableHeader().setResizingAllowed(true); + dataTableDialog = new StandardDialog(getTopLevel(), "Image Data", false); + dataTableDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setViewportView(dataTable); + scrollPane.setPreferredSize(new Dimension(300, 400)); + dataTableDialog.setContentPane(scrollPane); + dataTableDialog.pack(); + SwingUtils.centerComponent(getTopLevel(), dataTableDialog); + dataTableDialog.setVisible(true); + dataTableDialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + dataTableModel = null; + } + }); + } + } + + void updateStreamData() { + if ((dataTableModel != null) && (server != null)) { + StreamValue value = server.getValue(); + + List ids = (value == null) ? new ArrayList<>() : new ArrayList(value.getIdentifiers()); + if (ids.size() + 2 != dataTableModel.getRowCount()) { + dataTableModel.setNumRows(0); + try { + dataTableModel.addRow(new Object[]{"Locator", server.getUrl() + "/" + ((value == null) ? instanceName : server.getCurrentInstance())}); + } catch (Exception ex) { + dataTableModel.addRow(new Object[]{"Locator", ex.getMessage()}); + } + try { + dataTableModel.addRow(new Object[]{"Stream", server.getStreamAddress()}); + } catch (Exception ex) { + dataTableModel.addRow(new Object[]{"Stream", ex.getMessage()}); + } + Collections.sort(ids); + for (String id : ids) { + dataTableModel.addRow(new Object[]{id, ""}); + } + } + for (int i = 2; i < dataTableModel.getRowCount(); i++) { + String id = String.valueOf(dataTableModel.getValueAt(i, 0)); + Object obj = server.getValue(id); + if (obj != null) { + if (obj.getClass().isArray()) { + obj = obj.getClass().getComponentType().getSimpleName() + "[" + Array.getLength(obj) + "]"; + } else if (obj instanceof Double) { + obj = Convert.roundDouble((Double) obj, 1); + } else if (obj instanceof Float) { + obj = Convert.roundDouble((Float) obj, 1); + } + } + dataTableModel.setValueAt(String.valueOf(obj), i, 1); + } + } + } + + //////// + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonGroup1 = new javax.swing.ButtonGroup(); + buttonGroup2 = new javax.swing.ButtonGroup(); + buttonGroup3 = new javax.swing.ButtonGroup(); + buttonGroup4 = new javax.swing.ButtonGroup(); + jProgressBar1 = new javax.swing.JProgressBar(); + jPanel1 = new javax.swing.JPanel(); + jPanel7 = new javax.swing.JPanel(); + buttonMarker = new javax.swing.JToggleButton(); + buttonGrabBackground = new javax.swing.JButton(); + buttonSave = new javax.swing.JToggleButton(); + buttonFit = new javax.swing.JToggleButton(); + buttonReticle = new javax.swing.JToggleButton(); + buttonPause = new javax.swing.JToggleButton(); + buttonProfile = new javax.swing.JToggleButton(); + jPanel6 = new javax.swing.JPanel(); + textState = new javax.swing.JTextField(); + jLabel2 = new javax.swing.JLabel(); + comboCameras = new javax.swing.JComboBox(); + buttonConfig = new javax.swing.JButton(); + jLabel1 = new javax.swing.JLabel(); + buttonArgs = new javax.swing.JButton(); + buttonStop = new javax.swing.JButton(); + renderer = new ch.psi.pshell.imaging.Renderer(); + jPanel4 = new javax.swing.JPanel(); + jPanel3 = new javax.swing.JPanel(); + buttonZoomFit = new javax.swing.JRadioButton(); + buttonZoomStretch = new javax.swing.JRadioButton(); + buttonZoomNormal = new javax.swing.JRadioButton(); + buttonZoom025 = new javax.swing.JRadioButton(); + buttonZoom05 = new javax.swing.JRadioButton(); + buttonZoom2 = new javax.swing.JRadioButton(); + jPanel2 = new javax.swing.JPanel(); + checkHistogram = new javax.swing.JCheckBox(); + comboColormap = new javax.swing.JComboBox(); + jLabel3 = new javax.swing.JLabel(); + jLabel4 = new javax.swing.JLabel(); + buttonFullRange = new javax.swing.JRadioButton(); + buttonManual = new javax.swing.JRadioButton(); + buttonAutomatic = new javax.swing.JRadioButton(); + labelMin = new javax.swing.JLabel(); + spinnerMin = new javax.swing.JSpinner(); + spinnerMax = new javax.swing.JSpinner(); + labelMax = new javax.swing.JLabel(); + btFixColormapRange = new javax.swing.JButton(); + jPanel5 = new javax.swing.JPanel(); + buttonServer = new javax.swing.JRadioButton(); + buttonDirect = new javax.swing.JRadioButton(); + panelScreen = new javax.swing.JPanel(); + valueScreen = new ch.psi.pshell.swing.DeviceValuePanel(); + comboScreen = new javax.swing.JComboBox(); + panelScreen1 = new javax.swing.JPanel(); + valueFilter = new ch.psi.pshell.swing.DeviceValuePanel(); + comboFilter = new javax.swing.JComboBox(); + pauseSelection = new ch.psi.pshell.swing.ValueSelection(); + panelScreen2 = new javax.swing.JPanel(); + checkThreshold = new javax.swing.JCheckBox(); + spinnerThreshold = new javax.swing.JSpinner(); + checkBackground = new javax.swing.JCheckBox(); + checkGoodRegion = new javax.swing.JCheckBox(); + spinnerGrScale = new javax.swing.JSpinner(); + spinnerGrThreshold = new javax.swing.JSpinner(); + labelGrThreshold = new javax.swing.JLabel(); + labelGrScale = new javax.swing.JLabel(); + + setPreferredSize(new java.awt.Dimension(873, 600)); + + buttonMarker.setText("Marker"); + buttonMarker.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMarkerActionPerformed(evt); + } + }); + + buttonGrabBackground.setText("Grab Background"); + buttonGrabBackground.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonGrabBackgroundActionPerformed(evt); + } + }); + + buttonSave.setText("Save Snapshot"); + buttonSave.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonSaveActionPerformed(evt); + } + }); + + buttonFit.setSelected(true); + buttonFit.setText("Fit"); + buttonFit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonFitActionPerformed(evt); + } + }); + + buttonReticle.setSelected(true); + buttonReticle.setText("Reticle"); + buttonReticle.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonReticleActionPerformed(evt); + } + }); + + buttonPause.setText("Pause"); + buttonPause.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPauseActionPerformed(evt); + } + }); + + buttonProfile.setSelected(true); + buttonProfile.setText("Profile"); + buttonProfile.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonProfileActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel7Layout = new javax.swing.GroupLayout(jPanel7); + jPanel7.setLayout(jPanel7Layout); + jPanel7Layout.setHorizontalGroup( + jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel7Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(buttonPause) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonMarker) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonProfile) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonFit) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonReticle) + .addGap(18, 18, Short.MAX_VALUE) + .addComponent(buttonGrabBackground) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonSave) + .addGap(0, 0, 0)) + ); + + jPanel7Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonFit, buttonMarker, buttonPause, buttonProfile, buttonReticle}); + + jPanel7Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonGrabBackground, buttonSave}); + + jPanel7Layout.setVerticalGroup( + jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel7Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonPause) + .addComponent(buttonFit) + .addComponent(buttonMarker) + .addComponent(buttonSave) + .addComponent(buttonReticle) + .addComponent(buttonGrabBackground) + .addComponent(buttonProfile)) + .addGap(0, 0, 0)) + ); + + textState.setEditable(false); + textState.setHorizontalAlignment(javax.swing.JTextField.CENTER); + textState.setDisabledTextColor(new java.awt.Color(0, 0, 0)); + textState.setEnabled(false); + + jLabel2.setText("State:"); + + comboCameras.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N + comboCameras.setMaximumRowCount(30); + comboCameras.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + comboCamerasActionPerformed(evt); + } + }); + + buttonConfig.setText("Config"); + buttonConfig.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonConfigActionPerformed(evt); + } + }); + + jLabel1.setText("Camera:"); + + buttonArgs.setText("Setup"); + buttonArgs.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonArgsActionPerformed(evt); + } + }); + + buttonStop.setText("Stop"); + buttonStop.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonStopActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6); + jPanel6.setLayout(jPanel6Layout); + jPanel6Layout.setHorizontalGroup( + jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel6Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(comboCameras, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonArgs) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonConfig) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonStop) + .addGap(18, 18, 18) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(textState, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) + ); + + jPanel6Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonArgs, buttonConfig, buttonStop}); + + jPanel6Layout.setVerticalGroup( + jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel6Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(comboCameras, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel2) + .addComponent(textState, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonArgs) + .addComponent(buttonConfig) + .addComponent(buttonStop)) + .addGap(0, 0, 0)) + ); + + jPanel6Layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {comboCameras, textState}); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(renderer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel7, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel6, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jPanel6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(renderer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("Zoom")); + + buttonGroup1.add(buttonZoomFit); + buttonZoomFit.setText("Fit"); + buttonZoomFit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoomFitActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoomStretch); + buttonZoomStretch.setText("Stretch"); + buttonZoomStretch.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoomStretchActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoomNormal); + buttonZoomNormal.setText("Normal"); + buttonZoomNormal.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoomNormalActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoom025); + buttonZoom025.setText("1/4"); + buttonZoom025.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoom025ActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoom05); + buttonZoom05.setText("1/2"); + buttonZoom05.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoom05ActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoom2); + buttonZoom2.setText("2"); + buttonZoom2.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoom2ActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonZoomFit) + .addComponent(buttonZoomNormal) + .addComponent(buttonZoomStretch)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonZoom025) + .addComponent(buttonZoom05) + .addComponent(buttonZoom2)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonZoomNormal) + .addComponent(buttonZoom025)) + .addGap(0, 0, 0) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonZoomFit) + .addComponent(buttonZoom05)) + .addGap(0, 0, 0) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonZoomStretch) + .addComponent(buttonZoom2)) + .addContainerGap()) + ); + + jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("Colormap")); + + checkHistogram.setText("Histogram"); + checkHistogram.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkHistogramActionPerformed(evt); + } + }); + + comboColormap.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + onChangeColormap(evt); + } + }); + + jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel3.setText("Type:"); + + jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel4.setText("Range:"); + + buttonGroup3.add(buttonFullRange); + buttonFullRange.setText("Full"); + buttonFullRange.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + onChangeColormap(evt); + } + }); + + buttonGroup3.add(buttonManual); + buttonManual.setText("Manual"); + buttonManual.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + onChangeColormap(evt); + } + }); + + buttonGroup3.add(buttonAutomatic); + buttonAutomatic.setText("Automatic"); + buttonAutomatic.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + onChangeColormap(evt); + } + }); + + labelMin.setText("Min:"); + + spinnerMin.setModel(new javax.swing.SpinnerNumberModel(0, 0, 65535, 1)); + spinnerMin.setEnabled(false); + spinnerMin.setPreferredSize(new java.awt.Dimension(77, 20)); + spinnerMin.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + onChangeColormapRange(evt); + } + }); + + spinnerMax.setModel(new javax.swing.SpinnerNumberModel(255, 0, 65535, 1)); + spinnerMax.setEnabled(false); + spinnerMax.setPreferredSize(new java.awt.Dimension(77, 20)); + spinnerMax.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + onChangeColormapRange(evt); + } + }); + + labelMax.setText("Max:"); + + btFixColormapRange.setText("Fix"); + btFixColormapRange.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btFixColormapRangeActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel3) + .addComponent(jLabel4)) + .addGap(4, 4, 4) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonAutomatic) + .addComponent(buttonFullRange) + .addComponent(buttonManual) + .addComponent(comboColormap, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 12, Short.MAX_VALUE) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup() + .addComponent(labelMax) + .addGap(2, 2, 2) + .addComponent(spinnerMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(checkHistogram, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup() + .addComponent(labelMin) + .addGap(2, 2, 2) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(btFixColormapRange, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerMin, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addContainerGap()) + ); + + jPanel2Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btFixColormapRange, spinnerMax, spinnerMin}); + + jPanel2Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {jLabel3, jLabel4}); + + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(comboColormap, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel3) + .addComponent(checkHistogram)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonAutomatic) + .addComponent(jLabel4) + .addComponent(btFixColormapRange, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(labelMin) + .addComponent(spinnerMin, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonFullRange)) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonManual) + .addComponent(labelMax) + .addComponent(spinnerMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + jPanel5.setBorder(javax.swing.BorderFactory.createTitledBorder("Source")); + + buttonGroup4.add(buttonServer); + buttonServer.setSelected(true); + buttonServer.setText("Server"); + buttonServer.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonServerActionPerformed(evt); + } + }); + + buttonGroup4.add(buttonDirect); + buttonDirect.setText("Direct"); + buttonDirect.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonDirectActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5); + jPanel5.setLayout(jPanel5Layout); + jPanel5Layout.setHorizontalGroup( + jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel5Layout.createSequentialGroup() + .addContainerGap() + .addComponent(buttonServer) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonDirect) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel5Layout.setVerticalGroup( + jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel5Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonServer) + .addComponent(buttonDirect)) + .addContainerGap()) + ); + + panelScreen.setBorder(javax.swing.BorderFactory.createTitledBorder("Screen")); + + comboScreen.setEnabled(false); + comboScreen.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + comboScreenActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelScreenLayout = new javax.swing.GroupLayout(panelScreen); + panelScreen.setLayout(panelScreenLayout); + panelScreenLayout.setHorizontalGroup( + panelScreenLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelScreenLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelScreenLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(valueScreen, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(comboScreen, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + panelScreenLayout.setVerticalGroup( + panelScreenLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelScreenLayout.createSequentialGroup() + .addGap(4, 4, 4) + .addComponent(comboScreen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(valueScreen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + panelScreen1.setBorder(javax.swing.BorderFactory.createTitledBorder("Filter")); + + comboFilter.setEnabled(false); + comboFilter.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + comboFilterActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelScreen1Layout = new javax.swing.GroupLayout(panelScreen1); + panelScreen1.setLayout(panelScreen1Layout); + panelScreen1Layout.setHorizontalGroup( + panelScreen1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelScreen1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(panelScreen1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(valueFilter, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(comboFilter, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + panelScreen1Layout.setVerticalGroup( + panelScreen1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelScreen1Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addComponent(comboFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(valueFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pauseSelection.setDecimals(0); + + panelScreen2.setBorder(javax.swing.BorderFactory.createTitledBorder("Image")); + + checkThreshold.setText("Threshold"); + checkThreshold.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkThresholdActionPerformed(evt); + } + }); + + spinnerThreshold.setModel(new javax.swing.SpinnerNumberModel(0.0d, 0.0d, 99999.0d, 1.0d)); + spinnerThreshold.setPreferredSize(new java.awt.Dimension(77, 20)); + spinnerThreshold.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerThresholdonChange(evt); + } + }); + + checkBackground.setText("Subtract Background"); + checkBackground.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkBackgroundActionPerformed(evt); + } + }); + + checkGoodRegion.setText("Good Region"); + checkGoodRegion.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkGoodRegionActionPerformed(evt); + } + }); + + spinnerGrScale.setModel(new javax.swing.SpinnerNumberModel(3.0d, 0.01d, 100.0d, 1.0d)); + spinnerGrScale.setPreferredSize(new java.awt.Dimension(77, 20)); + spinnerGrScale.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerGrThresholdonChange(evt); + } + }); + + spinnerGrThreshold.setModel(new javax.swing.SpinnerNumberModel(0.5d, 0.04d, 1.0d, 0.1d)); + spinnerGrThreshold.setPreferredSize(new java.awt.Dimension(77, 20)); + spinnerGrThreshold.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerGrThresholdonChange(evt); + } + }); + + labelGrThreshold.setText("Threshold:"); + + labelGrScale.setText("Scale:"); + + javax.swing.GroupLayout panelScreen2Layout = new javax.swing.GroupLayout(panelScreen2); + panelScreen2.setLayout(panelScreen2Layout); + panelScreen2Layout.setHorizontalGroup( + panelScreen2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelScreen2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(panelScreen2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelScreen2Layout.createSequentialGroup() + .addComponent(checkGoodRegion) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(labelGrScale) + .addGap(2, 2, 2) + .addComponent(spinnerGrScale, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelScreen2Layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(labelGrThreshold) + .addGap(2, 2, 2) + .addComponent(spinnerGrThreshold, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(panelScreen2Layout.createSequentialGroup() + .addComponent(checkBackground) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(panelScreen2Layout.createSequentialGroup() + .addComponent(checkThreshold) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerThreshold, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + panelScreen2Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {spinnerGrScale, spinnerGrThreshold, spinnerThreshold}); + + panelScreen2Layout.setVerticalGroup( + panelScreen2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelScreen2Layout.createSequentialGroup() + .addGap(4, 4, 4) + .addComponent(checkBackground) + .addGap(2, 2, 2) + .addGroup(panelScreen2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkThreshold) + .addComponent(spinnerThreshold, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2) + .addGroup(panelScreen2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkGoodRegion) + .addComponent(spinnerGrScale, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelGrScale)) + .addGap(2, 2, 2) + .addGroup(panelScreen2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerGrThreshold, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelGrThreshold)) + .addContainerGap()) + ); + + javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(jPanel5, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelScreen, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelScreen1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pauseSelection, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(panelScreen2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jPanel5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelScreen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelScreen1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelScreen2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pauseSelection, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + ); + }// //GEN-END:initComponents + + private void comboCamerasActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comboCamerasActionPerformed + try { + if (!updatingCameraSelection) { + if (!comboCameras.isEnabled()) { + throw new Exception("Invalid state"); + } + comboCameras.setEnabled(false); + buttonServer.setEnabled(false); + buttonDirect.setEnabled(false); + final String cameraName = (String) comboCameras.getSelectedItem(); + new Thread(new Runnable() { + @Override + public void run() { + if (requestCameraListUpdate) { + requestCameraListUpdate = false; + try { + updateCameraList(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + try { + setCamera(cameraName); + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + updateStop(); + comboCameras.setEnabled(true); + buttonServer.setEnabled(true); + buttonDirect.setEnabled(true); + } + } + }).start(); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_comboCamerasActionPerformed + + private void buttonArgsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonArgsActionPerformed + try { + if (camera != null) { + String cameraConfigJson = null; + if (usingServer) { + String cameraServerUrl = server.getUrl().substring(0, server.getUrl().length() - 1) + "8"; + try (CameraServer srv = new CameraServer("CamServer", cameraServerUrl)) { + srv.initialize(); + //TODO: replace into encodeMultiline + cameraConfigJson = JsonSerializer.encode(srv.getConfig(cameraName), true); + } + + } else { + String configFolder = (String) getContext().getClassByName("SfCamera").getMethod("getConfigFolder", new Class[]{}).invoke(null); + Path configFile = Paths.get(configFolder, cameraName + ".json"); + cameraConfigJson = configFile.toFile().exists() ? new String(Files.readAllBytes(configFile)) : null; + } + TextEditor editor = new TextEditor(); + editor.setText(cameraConfigJson); + editor.setReadOnly(true); + editor.setTitle(cameraName); + EditorDialog dlg = editor.getDialog(getTopLevel(), false); + dlg.setSize(480, 640); + dlg.setVisible(true); + SwingUtils.centerComponent(getTopLevel(), dlg); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonArgsActionPerformed + + private void buttonConfigActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonConfigActionPerformed + try { + if (camera != null) { + this.showDeviceConfigDialog(camera, false); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonConfigActionPerformed + + private void buttonPauseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPauseActionPerformed + try { + renderer.removeOverlay(imageBufferOverlay); + if (camera != null) { + synchronized (imageBuffer) { + if (buttonPause.isSelected()) { + renderer.pause(); + } else { + imageBuffer.clear(); + renderer.resume(); + } + pauseSelection.setVisible(buttonPause.isSelected() && (imageBuffer.size() > 1)); + if (pauseSelection.isVisible()) { + renderer.addOverlay(imageBufferOverlay); + pauseSelection.setMaxValue(imageBuffer.size()); + pauseSelection.setValue(imageBuffer.size());; + } + } + updateStreamData(); + } + } catch (Exception ex) { + ex.printStackTrace(); + showException(ex); + } + }//GEN-LAST:event_buttonPauseActionPerformed + + private void buttonMarkerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMarkerActionPerformed + try { + checkMarker(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonMarkerActionPerformed + + private void buttonFitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonFitActionPerformed + try { + showFit = buttonFit.isSelected(); + if (showFit) { + renderer.setProfile(Renderer.Profile.None); + } else { + renderer.removeOverlays(fitOv); + fitOv = null; + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonFitActionPerformed + + private void buttonReticleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonReticleActionPerformed + try { + checkReticle(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonReticleActionPerformed + + private void buttonSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonSaveActionPerformed + try { + saveSnapshot(); + } catch (Exception ex) { + ex.printStackTrace(); + showException(ex); + } + }//GEN-LAST:event_buttonSaveActionPerformed + + private void buttonGrabBackgroundActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonGrabBackgroundActionPerformed + try { + if (camera != null) { + if (SwingUtils.showOption(getTopLevel(), "Background", "Do you want to capture background now?", OptionType.YesNo) == OptionResult.Yes) { + boolean laserOn = getLaserState(); + if (laserOn) { + setLaserState(false); + } + try { + System.out.println("Grabbing background for: " + cameraName); + if (server != null) { + server.captureBackground(5); + } else { + camera.captureBackground(5, 0); + } + } finally { + if (laserOn) { + setLaserState(true); + } + } + SwingUtils.showMessage(getTopLevel(), "Success", "Success capturing background", 5000); + } + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonGrabBackgroundActionPerformed + + private void buttonZoomFitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoomFitActionPerformed + try { + renderer.setMode(RendererMode.Fit); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonZoomFitActionPerformed + + private void buttonZoomStretchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoomStretchActionPerformed + try { + renderer.setMode(RendererMode.Stretch); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonZoomStretchActionPerformed + + private void buttonZoomNormalActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoomNormalActionPerformed + try { + renderer.setMode(RendererMode.Fixed); + centralizeRenderer(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonZoomNormalActionPerformed + + private void onChangeColormap(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_onChangeColormap + try { + if ((camera != null) && (camera instanceof ColormapSource) && !updatingColormap) { + ColormapSource source = (ColormapSource) camera; + Color colorReticule = new Color(16, 16, 16); + Color colorMarker = new Color(128, 128, 128); + source.getConfig().colormap = (Colormap) comboColormap.getSelectedItem(); + switch (source.getConfig().colormap) { + case Grayscale: + case Inverted: + colorReticule = new Color(0, 192, 0); + colorMarker = new Color(64, 255, 64); + break; + case Flame: + colorReticule = new Color(0, 192, 0); + colorMarker = new Color(64, 255, 64); + break; + } + + renderer.setPenReticle(new Pen(colorReticule)); + renderer.setPenProfile(new Pen(colorReticule, 0)); + renderer.setPenMarker(new Pen(colorMarker, 2)); + renderer.setShowReticle(false); + checkReticle(); + source.getConfig().colormapAutomatic = buttonAutomatic.isSelected(); + source.getConfig().colormapMin = buttonFullRange.isSelected() ? Double.NaN : (Integer) spinnerMin.getValue(); + source.getConfig().colormapMax = buttonFullRange.isSelected() ? Double.NaN : (Integer) spinnerMax.getValue(); + try { + source.getConfig().save(); + } catch (Exception ex) { + Logger.getLogger(ScreenPanel1.class.getName()).log(Level.WARNING, null, ex); + } + source.refresh(); + if (buttonPause.isSelected()) { + updatePause(); + } + updateColormap(); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_onChangeColormap + + private void onChangeColormapRange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_onChangeColormapRange + onChangeColormap(null); + }//GEN-LAST:event_onChangeColormapRange + + private void buttonZoom025ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoom025ActionPerformed + renderer.setZoom(0.25); + renderer.setMode(RendererMode.Zoom); + centralizeRenderer(); + }//GEN-LAST:event_buttonZoom025ActionPerformed + + private void buttonZoom05ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoom05ActionPerformed + renderer.setZoom(0.5); + renderer.setMode(RendererMode.Zoom); + centralizeRenderer(); + }//GEN-LAST:event_buttonZoom05ActionPerformed + + private void buttonServerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonServerActionPerformed + if (!usingServer) { + usingServer = true; + requestCameraListUpdate = true; + } + comboCamerasActionPerformed(null); + }//GEN-LAST:event_buttonServerActionPerformed + + private void buttonDirectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonDirectActionPerformed + if (usingServer) { + usingServer = false; + requestCameraListUpdate = true; + } + comboCamerasActionPerformed(null); + }//GEN-LAST:event_buttonDirectActionPerformed + + private void comboScreenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comboScreenActionPerformed + + comboScreen.setEnabled(false); + new Thread(new Runnable() { + @Override + public void run() { + ChannelInteger setpoint = null; + try { + int index = comboScreen.getSelectedIndex(); + if (cameraName.contains("DSRM")) { + setpoint = new ChannelInteger(null, cameraName + ":POSITION_SP"); + } else { + setpoint = new ChannelInteger(null, cameraName + ":SET_SCREEN1_POS"); + } + setpoint.initialize(); + if (setpoint.read() != index) { + setpoint.write(index); + //Must be threaded to control the laser because of sleep in setLaserState + /* + boolean laserOn = getLaserState(); + if (laserOn) { + setLaserState(false); + } + try { + setpoint.write(index); + } finally { + if (laserOn) { + setLaserState(true); + } + } + */ + } + screen.read(); + } catch (Exception ex) { + showException(ex); + } finally { + comboScreen.setEnabled(true); + if (setpoint != null) { + setpoint.close(); + } + } + } + }).start(); + }//GEN-LAST:event_comboScreenActionPerformed + + private void comboFilterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comboFilterActionPerformed + try { + String setpoint = (String) comboFilter.getSelectedItem(); + if (!setpoint.equals(filter.read())) { + filter.write(setpoint); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_comboFilterActionPerformed + + private void checkHistogramActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkHistogramActionPerformed + try { + setHistogramVisible(checkHistogram.isSelected()); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_checkHistogramActionPerformed + + private void buttonZoom2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoom2ActionPerformed + renderer.setZoom(2.0); + renderer.setMode(RendererMode.Zoom); + centralizeRenderer(); + }//GEN-LAST:event_buttonZoom2ActionPerformed + + private void spinnerThresholdonChange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerThresholdonChange + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + server.setThreshold((Double) spinnerThreshold.getValue()); + } + } catch (Exception ex) { + showException(ex); + updateServerControls(); + } + } + }//GEN-LAST:event_spinnerThresholdonChange + + private void checkBackgroundActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkBackgroundActionPerformed + if (server != null) { + if (!updatingServerControls) { + try { + if (server.isStarted()) { + server.setBackgroundSubtraction(checkBackground.isSelected()); + } + } catch (Exception ex) { + showException(ex); + updateServerControls(); + updatingServerControls = true; + checkBackground.setSelected(false); + updatingServerControls = false; + + } + } + } else { + camera.setBackgroundEnabled(checkBackground.isSelected()); + } + }//GEN-LAST:event_checkBackgroundActionPerformed + + private void checkThresholdActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkThresholdActionPerformed + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + spinnerThreshold.setVisible(checkThreshold.isSelected()); + server.setThreshold(checkThreshold.isSelected() ? (Double) spinnerThreshold.getValue() : null); + } + } catch (Exception ex) { + showException(ex); + updateServerControls(); + } + } + }//GEN-LAST:event_checkThresholdActionPerformed + + private void buttonStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonStopActionPerformed + try { + if (buttonStop.getText().equals("Stop")) { + if ((camera != null) && !camera.isClosed()) { + camera.close(); + } + } else { + if (isCameraStopped()) { + comboCamerasActionPerformed(null); + } + } + } catch (Exception ex) { + showException(ex); + } finally { + updateStop(); + } + }//GEN-LAST:event_buttonStopActionPerformed + + private void buttonProfileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonProfileActionPerformed + try { + showProfile = buttonProfile.isSelected(); + if (showProfile) { + renderer.setProfile(Renderer.Profile.None); + } else { + renderer.removeOverlays(profileOv); + profileOv = null; + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonProfileActionPerformed + + private void checkGoodRegionActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkGoodRegionActionPerformed + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + spinnerGrScale.setVisible(checkGoodRegion.isSelected()); + labelGrScale.setVisible(spinnerGrScale.isVisible()); + spinnerGrThreshold.setVisible(checkGoodRegion.isSelected()); + labelGrThreshold.setVisible(spinnerGrThreshold.isVisible()); + if (checkGoodRegion.isSelected()) { + server.setGoodRegion(((Number) spinnerGrThreshold.getValue()).doubleValue(), ((Number) spinnerGrScale.getValue()).doubleValue()); + } else { + server.setGoodRegion(null); + } + goodRegion = checkGoodRegion.isSelected(); + } + } catch (Exception ex) { + showException(ex); + ex.printStackTrace(); + updateServerControls(); + } + } + }//GEN-LAST:event_checkGoodRegionActionPerformed + + private void spinnerGrThresholdonChange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerGrThresholdonChange + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + server.setGoodRegion((Double) spinnerGrThreshold.getValue(), (Double) spinnerGrScale.getValue()); + } + } catch (Exception ex) { + showException(ex); + updateServerControls(); + } + } + }//GEN-LAST:event_spinnerGrThresholdonChange + + private void btFixColormapRangeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btFixColormapRangeActionPerformed + try { + updatingColormap = true; + ArrayProperties properties = currentFrame.data.getProperties(); + spinnerMax.setValue(properties.max.intValue()); + spinnerMin.setValue(properties.min.intValue()); + buttonManual.setSelected(true); + } catch (Exception ex) { + showException(ex); + } finally { + updatingColormap = false; + onChangeColormap(null); + } + }//GEN-LAST:event_btFixColormapRangeActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btFixColormapRange; + private javax.swing.JButton buttonArgs; + private javax.swing.JRadioButton buttonAutomatic; + private javax.swing.JButton buttonConfig; + private javax.swing.JRadioButton buttonDirect; + private javax.swing.JToggleButton buttonFit; + private javax.swing.JRadioButton buttonFullRange; + private javax.swing.JButton buttonGrabBackground; + private javax.swing.ButtonGroup buttonGroup1; + private javax.swing.ButtonGroup buttonGroup2; + private javax.swing.ButtonGroup buttonGroup3; + private javax.swing.ButtonGroup buttonGroup4; + private javax.swing.JRadioButton buttonManual; + private javax.swing.JToggleButton buttonMarker; + private javax.swing.JToggleButton buttonPause; + private javax.swing.JToggleButton buttonProfile; + private javax.swing.JToggleButton buttonReticle; + private javax.swing.JToggleButton buttonSave; + private javax.swing.JRadioButton buttonServer; + private javax.swing.JButton buttonStop; + private javax.swing.JRadioButton buttonZoom025; + private javax.swing.JRadioButton buttonZoom05; + private javax.swing.JRadioButton buttonZoom2; + private javax.swing.JRadioButton buttonZoomFit; + private javax.swing.JRadioButton buttonZoomNormal; + private javax.swing.JRadioButton buttonZoomStretch; + private javax.swing.JCheckBox checkBackground; + private javax.swing.JCheckBox checkGoodRegion; + private javax.swing.JCheckBox checkHistogram; + private javax.swing.JCheckBox checkThreshold; + private javax.swing.JComboBox comboCameras; + private javax.swing.JComboBox comboColormap; + private javax.swing.JComboBox comboFilter; + private javax.swing.JComboBox comboScreen; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private javax.swing.JPanel jPanel4; + private javax.swing.JPanel jPanel5; + private javax.swing.JPanel jPanel6; + private javax.swing.JPanel jPanel7; + private javax.swing.JProgressBar jProgressBar1; + private javax.swing.JLabel labelGrScale; + private javax.swing.JLabel labelGrThreshold; + private javax.swing.JLabel labelMax; + private javax.swing.JLabel labelMin; + private javax.swing.JPanel panelScreen; + private javax.swing.JPanel panelScreen1; + private javax.swing.JPanel panelScreen2; + private ch.psi.pshell.swing.ValueSelection pauseSelection; + private ch.psi.pshell.imaging.Renderer renderer; + private javax.swing.JSpinner spinnerGrScale; + private javax.swing.JSpinner spinnerGrThreshold; + private javax.swing.JSpinner spinnerMax; + private javax.swing.JSpinner spinnerMin; + private javax.swing.JSpinner spinnerThreshold; + private javax.swing.JTextField textState; + private ch.psi.pshell.swing.DeviceValuePanel valueFilter; + private ch.psi.pshell.swing.DeviceValuePanel valueScreen; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/ScreenPanel11.form b/plugins/ScreenPanel11.form new file mode 100644 index 0000000..c169e3f --- /dev/null +++ b/plugins/ScreenPanel11.form @@ -0,0 +1,1347 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/ScreenPanel11.java b/plugins/ScreenPanel11.java new file mode 100644 index 0000000..f6878b6 --- /dev/null +++ b/plugins/ScreenPanel11.java @@ -0,0 +1,4495 @@ +/* + * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved. + */ + +import ch.psi.pshell.bs.CameraServer; +import ch.psi.pshell.core.Context; +import java.io.IOException; +import java.nio.file.Paths; +import javax.swing.DefaultComboBoxModel; +import ch.psi.pshell.ui.Panel; +import ch.psi.pshell.imaging.ImageListener; +import ch.psi.utils.State; +import ch.psi.utils.Chrono; +import ch.psi.utils.swing.SwingUtils; +import ch.psi.utils.swing.TextEditor; +import ch.psi.pshell.bs.PipelineServer; +import ch.psi.pshell.bs.StreamValue; +import ch.psi.pshell.core.JsonSerializer; +import ch.psi.pshell.data.DataManager; +import ch.psi.pshell.device.Device; +import ch.psi.pshell.epics.ChannelInteger; +import ch.psi.pshell.epics.DiscretePositioner; +import ch.psi.pshell.epics.Epics; +import ch.psi.pshell.imaging.Colormap; +import ch.psi.pshell.imaging.ColormapSource; +import ch.psi.pshell.imaging.ColormapSource.ColormapSourceConfig; +import ch.psi.pshell.ui.App; +import ch.psi.pshell.imaging.Data; +import ch.psi.pshell.imaging.DimensionDouble; +import ch.psi.pshell.imaging.Histogram; +import ch.psi.pshell.imaging.ImageBuffer; +import ch.psi.pshell.imaging.Overlay; +import ch.psi.pshell.imaging.Overlays; +import ch.psi.pshell.imaging.Overlays.Text; +import ch.psi.pshell.imaging.Pen; +import ch.psi.pshell.imaging.PointDouble; +import ch.psi.pshell.imaging.Renderer; +import ch.psi.pshell.imaging.RendererListener; +import ch.psi.pshell.imaging.RendererMode; +import ch.psi.pshell.imaging.Source; +import ch.psi.pshell.scripting.InterpreterResult; +import ch.psi.pshell.scripting.ScriptManager; +import ch.psi.pshell.swing.DevicePanel; +import ch.psi.pshell.swing.ValueSelection; +import ch.psi.pshell.swing.ValueSelection.ValueSelectionListener; +import ch.psi.pshell.ui.Console; +import ch.psi.utils.Arr; +import ch.psi.utils.ArrayProperties; +import ch.psi.utils.Config; +import ch.psi.utils.Convert; +import ch.psi.utils.Str; +import ch.psi.utils.swing.Editor.EditorDialog; +import ch.psi.utils.swing.MainFrame; +import ch.psi.utils.swing.StandardDialog; +import ch.psi.utils.swing.StandardDialog.StandardDialogListener; +import ch.psi.utils.swing.SwingUtils.OptionResult; +import ch.psi.utils.swing.SwingUtils.OptionType; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingUtilities; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import org.apache.commons.math3.analysis.function.Gaussian; +import org.apache.commons.math3.fitting.GaussianCurveFitter; +import org.apache.commons.math3.fitting.PolynomialCurveFitter; +import org.apache.commons.math3.fitting.WeightedObservedPoint; +import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; + +/** + * + */ +public class ScreenPanel11 extends Panel { + + public static final String ARG_TYPE = "cam_type"; + public static final String ARG_LIST = "cam_list"; + public static final String ARG_ALIAS = "alias"; + + public static final String LASER_TYPE = "Laser"; + public static final String ELECTRONS_TYPE = "Electrons"; + public static final String PHOTONICS_TYPE = "Photonics"; + public static final String TWO_PULSES_TYPE = "2Pulses"; + + public static final int STACK_RETRIES = 10; + + final String CAMERA_DEVICE_NAME = "CurrentCamera"; + boolean useServerStats = true; + String userOverlaysConfigFile; + ColormapSource camera; + PipelineServer server; + String cameraName; + String cameraAlias; + int polling = 1000; + Overlay marker = null; + JDialog histogramDialog; + DiscretePositioner screen; + DiscretePositioner filter; + boolean showFit; + boolean showProfile; + Overlay[] userOv; + Overlay[] fitOv; + Overlay[] profileOv; + Overlay errorOverlay; + boolean requestCameraListUpdate; + boolean goodRegion; + boolean slicing; + String serverUrl; + String camServerUrl; + String instanceName; + Overlay titleOv = null; + Overlay backgOv = null; + boolean persistCameraState; + Map> groups; + Map aliases; + final Logger logger; + List types; + boolean isUrl; + boolean isPipeline; + DevicePanel streamPanel; + + public class CameraState extends Config { + + public boolean valid; + public boolean showSidePanel; + public boolean showProfile; + public boolean showFit; + public boolean showReticle; + public boolean showScale; + public boolean showTitle; + public double zoom; + public RendererMode mode; + public boolean colormapAutomatic; + public double colormapMin; + public double colormapMax; + public Colormap colormap; + public boolean colormapLogarithmic; + + String getFile() { + if (camera == null) { + return null; + } + return getContext().getSetup().expandPath("{context}/screen_panel/" + cameraName + ".properties"); + } + } + + void loadCameraState() { + if (persistCameraState) { + try { + CameraState state = new CameraState(); + state.load(state.getFile()); + if (state.valid) { + buttonSidePanel.setSelected(state.showSidePanel); + buttonSidePanelActionPerformed(null); + buttonProfile.setSelected(state.showProfile); + buttonProfileActionPerformed(null); + buttonFit.setSelected(state.showFit); + buttonFitActionPerformed(null); + buttonReticle.setSelected(state.showReticle); + buttonReticleActionPerformed(null); + buttonScale.setSelected(state.showScale); + buttonScaleActionPerformed(null); + buttonTitle.setSelected(state.showTitle); + buttonTitleActionPerformed(null); + renderer.setMode(state.mode); + renderer.setZoom(state.zoom); + if (camera instanceof ColormapSource) { + camera.getConfig().colormap = state.colormap; + camera.getConfig().colormapAutomatic = state.colormapAutomatic; + camera.getConfig().colormapLogarithmic = state.colormapLogarithmic; + camera.getConfig().colormapMax = state.colormapMax; + camera.getConfig().colormapMin = state.colormapMin; + updateColormap(); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + void saveCameraState() { + if (persistCameraState) { + try { + CameraState state = new CameraState(); + state.valid = true; + if (camera instanceof ColormapSource) { + state.colormap = camera.getConfig().colormap; + state.colormapAutomatic = camera.getConfig().colormapAutomatic; + state.colormapLogarithmic = camera.getConfig().colormapLogarithmic; + state.colormapMax = camera.getConfig().colormapMax; + state.colormapMin = camera.getConfig().colormapMin; + } + state.mode = renderer.getMode(); + state.zoom = renderer.getZoom(); + + state.showSidePanel = buttonSidePanel.isSelected(); + state.showProfile = buttonProfile.isSelected(); + state.showFit = buttonFit.isSelected(); + state.showReticle = buttonReticle.isSelected(); + state.showScale = buttonScale.isSelected(); + state.showTitle = buttonTitle.isSelected(); + + state.save(state.getFile()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + String pipelineSuffix = "_sp"; + + Double getServerDouble(String name) { + return (Double) Convert.toDouble(server.getValue(name)); + } + + double[] getServerDoubleArray(String name) { + return (double[]) Convert.toDouble(server.getValue(name)); + } + + class ImageData { + + ImageData() { + if (server != null) { + cache = server.getStream().take(); + String prefix = goodRegion ? "gr_" : ""; + x_fit_mean = getDouble(prefix + "x_fit_mean"); + y_fit_mean = getDouble(prefix + "y_fit_mean"); + x_fit_standard_deviation = getDouble(prefix + "x_fit_standard_deviation"); + y_fit_standard_deviation = getDouble(prefix + "y_fit_standard_deviation"); + x_fit_gauss_function = getDoubleArray(prefix + "x_fit_gauss_function"); + y_fit_gauss_function = getDoubleArray(prefix + "y_fit_gauss_function"); + x_profile = getDoubleArray("x_profile"); + y_profile = getDoubleArray("y_profile"); + x_center_of_mass = getDouble("x_center_of_mass"); + y_center_of_mass = getDouble("y_center_of_mass"); + x_rms = getDouble("x_rms"); + y_rms = getDouble("y_rms"); + if (goodRegion) { + double[] gX2 = new double[x_profile.length]; + Arrays.fill(gX2, Double.NaN); + try { + double[] axis = getDoubleArray("x_axis"); + gr_x_axis = getDoubleArray("gr_x_axis"); + double x = gr_x_axis[0]; + gr_size_x = x_fit_gauss_function.length; + //If gr axis values are not identical, calculate the index... + gr_pos_x = (int) ((renderer.getCalibration() != null) ? renderer.getCalibration().convertToImageX(x) : x); + //But prefer checking the value to avoid raounding errors + for (int i=0;i imageBuffer = new ArrayList(); + Frame currentFrame; + int imageBufferLenght = 1; + Text imagePauseOverlay; + final Console console; + + public ScreenPanel11() { + logger = Logger.getLogger(getClass().getName()); + try { + initComponents(); + panelPulse.setVisible(false); + panelScreen.setVisible(false); + panelFilter.setVisible(false); + spinnerBackground.setVisible(false); + spinnerThreshold.setVisible(false); + btFixColormapRange.setVisible(false); + setGoodRegionOptionsVisible(false); + setSlicingOptionsVisible(false); + setRotationOptionsVisible(false); + setAveragingOptionsVisible(false); + JComponent editor = spinnerSlOrientation.getEditor(); + if (editor instanceof JSpinner.DefaultEditor) { + ((JSpinner.DefaultEditor) editor).getTextField().setHorizontalAlignment(JTextField.RIGHT); + } + renderer.setPersistenceFile(Paths.get(getContext().getSetup().getContextPath(), "Renderer_Cameras.bin")); + //setPersistedComponents(new Component[]{buttonServer, buttonDirect}); + setPersistedComponents(new Component[]{buttonTitle}); + comboCameras.setEnabled(false); + comboType.setEnabled(false); + if (App.hasArgument(ARG_LIST)){ + comboType.setVisible(false); + labelType.setVisible(false); + } + if (App.hasArgument(ARG_TYPE)){ + types = new ArrayList<>(); + for (String token : App.getArgumentValue(ARG_TYPE).split(",")){ + if (!token.isBlank()){ + types.add(token.trim()); + } + } + } + + SwingUtils.setEnumCombo(comboColormap, Colormap.class); + if (App.hasArgument("poll")) { + try { + polling = Integer.valueOf(App.getArgumentValue("poll")); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + if (App.hasArgument("zoom")) { + try { + renderer.setDefaultZoom(Double.valueOf(App.getArgumentValue("zoom"))); + renderer.resetZoom(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + if (App.hasArgument("buf")) { + try { + imageBufferLenght = Integer.valueOf(App.getArgumentValue("buf")); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + if (App.hasArgument("usr_ov")) { + try { + userOverlaysConfigFile = App.getArgumentValue("usr_ov"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + if (App.hasArgument("srv_url")) { + serverUrl = App.getArgumentValue("srv_url"); + } + + if (App.hasArgument("cam_srv_url")) { + camServerUrl = App.getArgumentValue("cam_srv_url"); + } + + if (App.hasArgument("calc")) { + useServerStats = false; + } + + if (App.hasArgument("persist")) { + persistCameraState = true; + } + + if (App.hasArgument("suffix")) { + pipelineSuffix = App.getArgumentValue("suffix"); + } + + renderer.setProfileNormalized(true); + renderer.setShowProfileLimits(false); + + JMenuItem menuCalibrate = new JMenuItem("Calibrate..."); + menuCalibrate.addActionListener((ActionEvent e) -> { + try { + calibrate(); + } catch (Exception ex) { + showException(ex); + } + }); + + JMenuItem menuRendererConfig = new JMenuItem("Renderer Parameters"); + menuRendererConfig.addActionListener((ActionEvent e) -> { + try { + if (camera != null) { + this.showDeviceConfigDialog(camera, false); + } + } catch (Exception ex) { + showException(ex); + } + }); + + JMenuItem menuCameraConfig = new JMenuItem("Camera Configurarion"); + menuCameraConfig.addActionListener((ActionEvent e) -> { + try { + if (camera != null) { + String cameraConfigJson = null; + String cameraServerUrl = (camServerUrl == null) ? server.getUrl().substring(0, server.getUrl().length() - 1) + "8" : camServerUrl; + try (CameraServer srv = newCameraServer()) { + //TODO: replace into encodeMultiline + cameraConfigJson = JsonSerializer.encode(srv.getConfig(cameraName), true); + } + + TextEditor configEditor = new TextEditor(); + configEditor.setText(cameraConfigJson); + configEditor.setReadOnly(true); + configEditor.setTitle(cameraName); + EditorDialog dlg = configEditor.getDialog(getTopLevel(), false); + dlg.setSize(480, 640); + dlg.setVisible(true); + SwingUtils.centerComponent(getTopLevel(), dlg); + } + } catch (Exception ex) { + showException(ex); + } + }); + + JMenuItem menuSetImageBufferSize = new JMenuItem("Set Stack Size..."); + menuSetImageBufferSize.addActionListener((ActionEvent e) -> { + try { + String ret = SwingUtils.getString(getTopLevel(), "Enter size of image buffer: ", String.valueOf(imageBufferLenght)); + if (ret != null) { + this.setImageBufferSize(Integer.valueOf(ret)); + } + } catch (Exception ex) { + showException(ex); + } + }); + + JMenuItem menuSaveStack = new JMenuItem("Save Stack"); + menuSaveStack.addActionListener((ActionEvent e) -> { + try { + boolean completeOnly = true; + boolean badFormat = false; + boolean incomplete = false; + + while ((incomplete=((imageBuffer.size() < imageBufferLenght) && completeOnly)) || (badFormat = !hasSameFormat(imageBuffer))){ + if (incomplete){ + OptionResult ret = SwingUtils.showOption(getTopLevel(), "Save Stack", String.format("Stack is not complete (%d/%d images). Wait for completion?", imageBuffer.size(), imageBufferLenght), OptionType.YesNoCancel); + if (ret == OptionResult.Cancel){ + return; + } + if (ret == OptionResult.Yes){ + for (int i=0; i { + renderer.abortSelection(); + if (server != null) { + final Overlays.Rect selection = new Overlays.Rect(renderer.getPenMovingOverlay()); + renderer.addListener(new RendererListener() { + @Override + public void onSelectionFinished(Renderer renderer, Overlay overlay) { + try { + renderer.setShowReticle(false); + Rectangle roi = overlay.isFixed() ? renderer.toImageCoord(overlay.getBounds()) : overlay.getBounds(); + if (server.isRoiEnabled()) { + int[] cur = server.getRoi(); + server.setRoi(new int[]{roi.x + cur[0], roi.y + cur[1], roi.width, roi.height}); + } else { + server.setRoi(new int[]{roi.x, roi.y, roi.width, roi.height}); + } + } catch (Exception ex) { + } finally { + renderer.removeListener(this); + } + } + + @Override + public void onSelectionAborted(Renderer renderer, Overlay overlay) { + renderer.removeListener(this); + } + }); + selection.setFixed(true); + renderer.startSelection(selection); + } + }); + + JMenuItem menuResetROI = new JMenuItem("Reset ROI"); + menuResetROI.addActionListener((ActionEvent e) -> { + renderer.abortSelection(); + if (server != null) { + try { + renderer.setShowReticle(false); + server.resetRoi(); + } catch (IOException ex) { + showException(ex); + } + } + }); + + renderer.getPopupMenu().addSeparator(); + renderer.getPopupMenu().add(menuRendererConfig); + renderer.getPopupMenu().add(menuCameraConfig); + renderer.getPopupMenu().add(menuSetImageBufferSize); + renderer.getPopupMenu().add(menuSaveStack); + renderer.getPopupMenu().addSeparator(); + renderer.getPopupMenu().add(menuCalibrate); + renderer.getPopupMenu().addSeparator(); + renderer.getPopupMenu().add(menuSetROI); + renderer.getPopupMenu().add(menuResetROI); + renderer.getPopupMenu().addPopupMenuListener(new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + menuResetROI.setEnabled(server != null); + menuSetROI.setEnabled(server != null); + menuCalibrate.setVisible(server != null); + menuCalibrate.setEnabled((calibrationDialolg == null) || (!calibrationDialolg.isShowing())); + menuSaveStack.setEnabled(imageBufferLenght > 0); + menuSetImageBufferSize.setEnabled(!renderer.isPaused()); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + } + }); + renderer.getPopupMenu().setVisible(false); + buttonScale.setSelected(renderer.getShowColormapScale()); + clearMarker(); + + showFit = buttonFit.isSelected(); + showProfile = buttonProfile.isSelected(); + + pauseSelection.setVisible(false); + pauseSelection.setMinValue(1); + pauseSelection.addListener(new ValueSelectionListener() { + @Override + public void onValueChanged(ValueSelection origin, double value, boolean editing) { + if (editing && (value >= 1) && (value <= imageBuffer.size())) { + updatePause(); + } + } + }); + renderer.addListener(new RendererListener() { + @Override + public void onMoveFinished(Renderer renderer, Overlay overlay) { + if (overlay == marker) { + try { + onMarkerChanged(); + } catch (IOException ex) { + logger.log(Level.WARNING, null, ex); + } + } + } + }); + if (MainFrame.isDark()) { + //textState.setDisabledTextColor(textState.getForeground()); + } + + } catch (Exception ex) { + ex.printStackTrace(); + } + + console = (!App.hasArgument("console")) ? null : new Console() { + + @Override + protected void onConsoleCommand(String command) { + String[] tokens = command.split(" "); + if (tokens.length > 0){ + try { + if (tokens[0].equals("cam")) { + if (!tokens[1].equals(comboCameras.getSelectedItem())) { + setComboTypeSelection("All"); + updateCameraList(); + comboCameras.setSelectedItem(tokens[1]); + if (!tokens[1].equals(comboCameras.getSelectedItem())) { + comboCameras.setSelectedItem(""); + throw new Exception("Invalid camera name : " + tokens[1]); + } + System.out.println("Console set camera: " + tokens[1]); + } + } else if (tokens[0].equals("pip")) { + initPipeline(tokens[1]); + } else if (tokens[0].equals("str")) { + initStream(tokens[1]); + } else if (tokens[0].equals("stop")) { + initCamera(""); + } else { + System.err.println("Invalid command: " + command); + } + } catch (Exception ex) { + System.err.println(ex); + } + } + } + }; + } + + @Override + public void onStart() { + super.onStart(); + updateDialogTitle(); + if (App.hasArgument("console")) { + console.start(); + } + } + + @Override + public void onStop() { + try { + if (camera != null) { + saveCameraState(); + camera.close(); + camera = null; + server = null; + updateButtons(); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + try { + if (console != null) { + console.stop(); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + super.onStop(); + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + comboCameras.setEnabled(false); + comboType.setEnabled(false); + + String[] types = new String[]{"All"}; + try (CameraServer srv = newCameraServer()) { + groups = srv.getCameraGroups(); + types = Arr.insert(Arr.sort(groups.keySet().toArray(new String[0])), "All", 0); + aliases = srv.getCameraAliases(); + } catch (Exception ex){ + groups = null; + aliases = null; + } + if (this.types!=null){ + types = this.types.toArray(new String[0]); + } + comboType.setModel(new javax.swing.DefaultComboBoxModel(types)); + + comboCameras.setEnabled(true); + comboType.setEnabled(true); + setComboCameraSelection(null); + if (Arr.containsEqual(types, "All")){ + setComboTypeSelection("All"); + } + updateCameraList(); + + try{ + if (App.hasArgument("s")) { + initSimulation(); + } else if (App.hasArgument("str")) { + initStream(App.getArgumentValue("str")); + } else if (App.hasArgument("pip")) { + initPipeline(App.getArgumentValue("pip")); + } else { + if (comboCameras.getModel().getSize() > 0) { + if (App.hasArgument("cam")) { + initCamera(App.getArgumentValue("cam")); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + updateButtons(); + startTimer(1000); + } + + public void initCamera(String camera) throws IOException, InterruptedException{ + setComboCameraSelection(camera); + comboCamerasActionPerformed(null); + } + + public void initStream(String url) throws IOException, InterruptedException{ + if (!url.contains("\\")){ + url="tcp://" + url; + } + setCamera(url); + } + + public void initPipeline(String url) throws IOException, InterruptedException{ + if (!url.contains("\\")){ + url="cs://" + url; + } + setCamera(url); + } + + public void initSimulation() throws IOException, InterruptedException{ + renderer.setDevice((Source) getDevice("image")); + renderer.setAutoScroll(true); + ((Source) getDevice("image")).addListener(new ImageListener() { + @Override + public void onImage(Object o, BufferedImage bi, Data data) { + manageFit(bi, data); + manageUserOverlays(bi, data); + } + + @Override + public void onError(Object o, Exception ex) { + } + } + ); + } + + boolean isVisible(String camera) { + return ((comboType.getSelectedItem().toString().toLowerCase().equals("all")) || (getCameraTypes(camera).contains(comboType.getSelectedItem()))); + } + + DefaultComboBoxModel getCameraList() throws Exception { + DefaultComboBoxModel model = new DefaultComboBoxModel(); + if (App.hasArgument(ARG_LIST)){ + List lists = App.getArgumentValues(ARG_LIST); + for (String list: lists){ + String[] tokens = list.split(","); + for (String token : tokens){ + if (!token.isBlank()){ + model.addElement(token.trim()); + } + } + } + } else { + try (CameraServer srv = newCameraServer()) { + List cameras = srv.getCameras(); + Collections.sort(cameras); + if (App.hasArgument(ARG_ALIAS) && (aliases!=null)){ + if ("only".equals(App.getArgumentValue(ARG_ALIAS))){ + cameras = new ArrayList(); + } + cameras.addAll(0, aliases.keySet()); + } + for (String camera : cameras) { + if (isVisible(camera)) { + model.addElement(camera); + } + } + } + } + if (App.hasArgument("cam")) { + String camera = App.getArgumentValue("cam"); + if (model.getIndexOf(camera) < 0) { + if (isVisible(camera)) { + model.addElement(camera); + } + } + } + model.addElement(""); + + return model; + } + + PipelineServer newServer() throws IOException { + if (serverUrl != null) { + System.out.println("Connecting to server: " + serverUrl); + server = new PipelineServer(CAMERA_DEVICE_NAME, serverUrl); + } else { + System.out.println("Connecting to server"); + server = new PipelineServer(CAMERA_DEVICE_NAME); + } + updateButtons(); + return server; + } + + CameraServer newCameraServer() throws IOException, InterruptedException { + String cameraServerUrl = (camServerUrl == null) ? server.getUrl().substring(0, server.getUrl().length() - 1) + "8" : camServerUrl; + CameraServer srv = new CameraServer("CamServer", cameraServerUrl); + srv.initialize(); + return srv; + } + + boolean updatingCameraSelection; + + void setComboCameraSelection(Object selection) { + updatingCameraSelection = true; + try { + comboCameras.setSelectedItem(selection); + } finally { + updatingCameraSelection = false; + } + } + + void setComboTypeSelection(Object selection) { + updatingCameraSelection = true; + try { + comboType.setSelectedItem(selection); + } finally { + updatingCameraSelection = false; + } + } + + void updateCameraList() { + try { + String selected = (String) comboCameras.getSelectedItem(); + comboCameras.setModel(getCameraList()); + if ((selected != null) && (((DefaultComboBoxModel) comboCameras.getModel()).getIndexOf(selected) > 0)){ + setComboCameraSelection(selected); + } else{ + setComboCameraSelection(""); + } + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + updateButtons(); + } + } + + final Object lockOverlays = new Object(); + + void manageFit(BufferedImage bi, Data data) { + Overlay[][] fo = null; + if ((showFit || showProfile)) { + try { + fo = getFitOverlays(data); + } catch (Exception ex) { + System.err.println(ex); + } + } + synchronized (lockOverlays) { + fo = (fo == null) ? new Overlay[][]{null, null} : fo; + renderer.updateOverlays(fo[0], profileOv); + profileOv = fo[0]; + renderer.updateOverlays(fo[1], fitOv); + fitOv = fo[1]; + } + } + + void manageUserOverlays(BufferedImage bi, Data data) { + Overlay[] fo = (bi == null) ? null : getUserOverlays(data); + synchronized (lockOverlays) { + renderer.updateOverlays(fo, userOv); + userOv = fo; + } + } + + void updateDialogTitle() { + if (App.isDetached()) { + getTopLevel().setTitle(cameraName == null ? "ScreenPanel" : cameraName); + } + } + + void manageTitleOverlay() { + Overlay to = null; + String text = cameraAlias; + if ((buttonTitle.isSelected()) && (text != null)) { + Font font = new Font("Arial", Font.PLAIN, 28); + to = new Text(renderer.getPenErrorText(), text, font, new Point(-SwingUtils.getTextSize(text, renderer.getGraphics().getFontMetrics(font)).width - 14, 26)); + to.setFixed(true); + to.setAnchor(Overlay.ANCHOR_VIEWPORT_OR_IMAGE_TOP_RIGHT); + } + + synchronized (lockOverlays) { + renderer.updateOverlays(to, titleOv); + titleOv = to; + } + } + + + void manageOverlayErrorOverlay(boolean error) { + if ((error) != (backgOv != null)){ + + Overlay bo = null; + if (error){ + String text = "Invalid image background"; + Font font = new Font("Arial", Font.PLAIN, 14); + bo = new Text(renderer.getPenErrorText(), text, font, new Point(-SwingUtils.getTextSize(text, renderer.getGraphics().getFontMetrics(font)).width - 14, 46)); + bo.setFixed(true); + bo.setAnchor(Overlay.ANCHOR_VIEWPORT_OR_IMAGE_TOP_RIGHT); + } + synchronized (lockOverlays) { + renderer.updateOverlays(bo,backgOv); + backgOv = bo; + } + } + } + + + @Override + public void onStateChange(State state, State former) { + + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + //Callback to perform update - in event thread + @Override + protected void doUpdate() { + } + + Thread devicesInitTask; + + void setCamera(String cameraName) throws IOException, InterruptedException { + System.out.println("Initializing: " + cameraName); + isUrl = (cameraName!=null) && cameraName.startsWith("tcp://"); + isPipeline = (cameraName!=null) && cameraName.startsWith("cs://"); + if (isPipeline) { + cameraName = cameraName.substring(cameraName.lastIndexOf("/") + 1); + } + panelPipeline.setVisible(!isUrl); + panelCameraSelection.setVisible(!isUrl); + parseUserOverlays(); + errorOverlay = null; + lastMarkerPos = null; + lastFrame = null; + lastPipelinePars = null; + + if (streamPanel!=null){ + streamPanel.hideDevicePanel(server.getStream()); + } + + if (calibrationDialolg != null) { + calibrationDialolg.dispose(); + calibrationDialolg = null; + } + + if (camera != null) { + saveCameraState(); + camera.close(); + camera = null; + server = null; + } + updateButtons(); + instanceName = null; + renderer.setDevice(null); + renderer.setShowReticle(false); + renderer.removeOverlays(fitOv); + renderer.removeOverlays(profileOv); + renderer.removeOverlays(userOv); + renderer.clear(); + renderer.resetZoom(); + + cameraAlias = cameraName; + if ((aliases!=null) && (cameraName!=null) && (aliases.containsKey(cameraName))){ + cameraName = aliases.get(cameraName); + System.out.println("Camera name is alias to: " + cameraName); + } + + boolean changed = !String.valueOf(cameraName).equals(this.cameraName); + this.cameraName = cameraName; + + if (changed) { + spinnerBackground.setVisible(false); + spinnerThreshold.setVisible(false); + checkThreshold.setEnabled(false); + checkRotation.setEnabled(false); + checkAveraging.setEnabled(false); + checkGoodRegion.setEnabled(false); + setGoodRegionOptionsVisible(false); + setSlicingOptionsVisible(false); + setRotationOptionsVisible(false); + setAveragingOptionsVisible(false); + } + synchronized (imageBuffer) { + currentFrame = null; + imageBuffer.clear(); + } + if (changed) { + checkBackground.setEnabled(false); + if ((devicesInitTask != null) && (devicesInitTask.isAlive())) { + devicesInitTask.interrupt(); + } + if (screen != null) { + screen.close(); + screen = null; + } + if (filter != null) { + filter.close(); + filter = null; + } + if (renderer.isPaused()) { + renderer.resume(); + removePauseOverlay(); + pauseSelection.setVisible(false); + panelCameraSelection.setVisible(true); + } + } + manageTitleOverlay(); + updateDialogTitle(); + + if (cameraName == null) { + return; + } + try { + + camera = newServer(); + camera.getConfig().flipHorizontally = false; + camera.getConfig().flipVertically = false; + camera.getConfig().rotation = 0.0; + camera.getConfig().roiX = 0; + camera.getConfig().roiY = 0; + camera.getConfig().roiWidth = -1; + camera.getConfig().roiHeight = -1; + + camera.initialize(); + camera.assertInitialized(); + System.out.println("Camera initialization OK"); + loadCameraState(); + if (server != null) { + //server.start(cameraName, false); + if (isUrl){ + System.out.println("Setting url: " + cameraName ); + instanceName = cameraName; + server.setStreamSocket(instanceName); + server.startReceiver(); + cameraName = null; + } else if (isPipeline){ + System.out.println("Setting pipeline: " + cameraName ); + instanceName = cameraName; + server.start(instanceName, true); + try{ + cameraName = server.getCameraName(); + //setComboCameraSelection(cameraName); + } catch (Exception ex){ + cameraName = null; + } + System.out.println("Camera Name: " + cameraName ); + } else { + System.out.println("Setting camera: " + cameraName ); + String pipelineName = cameraName + pipelineSuffix; + instanceName = cameraName + pipelineSuffix + "1"; + if (!server.getPipelines().contains(pipelineName)) { + System.out.println("Creating pipeline: " + pipelineName); + HashMap config = new HashMap<>(); + config.put("camera_name", cameraName); + //server.createFromConfig(config, pipelineName); + server.savePipelineConfig(pipelineName, config); + } + server.start(pipelineName, instanceName); + } + this.cameraName = cameraName; + updatePipelineControls(); + checkThreshold.setEnabled(true); + checkRotation.setEnabled(true); + checkAveraging.setEnabled(true); + checkGoodRegion.setEnabled(true); + } else { + checkThreshold.setSelected(false); + checkRotation.setSelected(false); + checkAveraging.setSelected(false); + checkGoodRegion.setSelected(false); + if (polling <= 0) { + camera.setMonitored(true); + } else { + camera.setPolling(polling); + } + camera.setBackgroundEnabled(checkBackground.isSelected()); + } + updateButtons(); + camera.getConfig().save(); + + renderer.setDevice(camera); + + renderer.setAutoScroll(true); + //renderer.setMarker(marker); + clearMarker(); + imageSize = null; + + camera.addListener(new ImageListener() { + @Override + public void onImage(Object o, BufferedImage bi, Data data) { + if (bi != null) { + if ((imageSize == null) || imageSize.width != bi.getWidth() || imageSize.height != bi.getHeight()) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if ((renderer.getMode() == RendererMode.Zoom) || (renderer.getMode() == RendererMode.Fixed)) { + centralizeRenderer(); + } + checkReticle(); + } + }); + imageSize = new Dimension(bi.getWidth(), bi.getHeight()); + } + renderer.setProfileSize(Math.min(bi.getWidth(), bi.getHeight())); + } + //renderer.setCalibration(camera.getCalibration()); + if (!renderer.isPaused()) { + if (data != null) { + synchronized (imageBuffer) { + currentFrame = new Frame(data); + if (imageBufferLenght >= 1) { + imageBuffer.add(currentFrame); + if (imageBuffer.size() > imageBufferLenght) { + imageBuffer.remove(0); + setBufferFull(true); + } else { + setBufferFull(false); + } + } else { + setBufferFull(true); + } + updateMarker(); + } + } + manageFit(bi, data); + manageUserOverlays(bi, data); + } + //updateImageData(); + } + + @Override + public void onError(Object o, Exception ex) { + //System.err.println(ex); + } + }); + + } catch (Exception ex) { + showException(ex); + renderer.clearOverlays(); + updatePipelineControls(); + if (renderer.getDevice() == null) { + //renderer.setZoom(1.0); + //renderer.setMode(RendererMode.Zoom); + errorOverlay = new Text(renderer.getPenErrorText(), ex.toString(), new Font("Verdana", Font.PLAIN, 12), new Point(20, 20)); + errorOverlay.setFixed(true); + errorOverlay.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_LEFT); + renderer.addOverlay(errorOverlay); + } + } finally { + //checkReticle(); + onTimer(); + } + onChangeColormap(null); + checkBackground.setEnabled(true); + if (changed) { + if (cameraName==null){ + + } + boolean electrons = (this.cameraName!=null) && getCameraTypes(this.cameraName).contains(ELECTRONS_TYPE); + boolean twoPulses = (this.cameraName!=null) && getCameraTypes(this.cameraName).contains(TWO_PULSES_TYPE); + comboScreen.setModel(new DefaultComboBoxModel()); + comboScreen.setEnabled(false); + comboFilter.setModel(new DefaultComboBoxModel()); + comboFilter.setEnabled(false); + panelFilter.setVisible(electrons); + panelScreen.setVisible(electrons); + panelPulse.setVisible(twoPulses); + textPulse.setText(""); + if (this.cameraName!=null){ + if (electrons) { + //Parallelizing initialization + devicesInitTask = new Thread(() -> { + try { + if (this.cameraName.contains("DSRM")) { + screen = new DiscretePositioner("CurrentScreen", this.cameraName + ":POSITION_SP", this.cameraName + ":POSITION"); + } else { + screen = new DiscretePositioner("CurrentScreen", this.cameraName + ":SET_SCREEN1_POS", this.cameraName + ":GET_SCREEN1_POS"); + } + screen.setMonitored(true); + screen.initialize(); + DefaultComboBoxModel model = new DefaultComboBoxModel(); + for (String pos : screen.getPositions()) { + model.addElement(pos); + } + comboScreen.setModel(model); + comboScreen.setSelectedItem(screen.read()); + + } catch (Exception ex) { + comboScreen.setModel(new DefaultComboBoxModel()); + System.err.println(ex.getMessage()); + screen = null; + } + comboScreen.setEnabled(screen != null); + valueScreen.setDevice(screen); + + try { + filter = new DiscretePositioner("CurrentFilter", this.cameraName + ":SET_FILTER", this.cameraName + ":GET_FILTER"); + filter.setMonitored(true); + filter.initialize(); + DefaultComboBoxModel model = new DefaultComboBoxModel(); + for (String pos : filter.getPositions()) { + model.addElement(pos); + } + comboFilter.setModel(model); + comboFilter.setSelectedItem(filter.read()); + } catch (Exception ex) { + System.err.println(ex.getMessage()); + filter = null; + } + comboFilter.setEnabled(filter != null); + valueFilter.setDevice(filter); + }); + devicesInitTask.start(); + } + } + } + } + + boolean bufferFull = true; + + void setBufferFull(boolean value) { + if (value != bufferFull) { + SwingUtilities.invokeLater(() -> { + buttonPause.setBackground(value ? buttonSave.getBackground() : buttonSave.getBackground().brighter()); + }); + bufferFull = value; + } + } + + volatile Dimension imageSize; + + void checkReticle() { + if ((renderer.getDevice() != null) && (camera != null) && (camera.getConfig().isCalibrated()) && buttonReticle.isSelected()) { + //renderer.setCalibration(camera.getCalibration()); + renderer.configureReticle(new Dimension(800, 800), 200); + renderer.setShowReticle(true); + } else { + //renderer.setCalibration(null); + renderer.setShowReticle(false); + } + renderer.refresh(); + } + + void checkMarker(Point p) throws IOException { + if (camera != null) { + if (buttonMarker.isSelected()) { + Dimension d = renderer.getImageSize(); + if (p == null) { + p = (d == null) ? new Point(renderer.getWidth() / 2, renderer.getHeight() / 2) : new Point(d.width / 2, d.height / 2); + } + Overlay ov = null; + marker = new Overlays.Crosshairs(renderer.getPenMarker(), p, new Dimension(100, 100)); + marker.setMovable(true); + marker.setPassive(false); + } else { + marker = null; + } + renderer.setMarker(marker); + onMarkerChanged(); + } + } + + Point lastMarkerPos; + + void onMarkerChanged() throws IOException { + lastMarkerPos = getStreamMarkerPos(); + if (marker == null) { + setInstanceConfigValue("Marker", null); + } else { + setInstanceConfigValue("Marker", new int[]{marker.getPosition().x, marker.getPosition().y}); + } + } + + void updateMarker() { + try { + if ((server != null) && (!isUrl)) { + Point p = getStreamMarkerPos(); + if (p != null) { + //To prevent a local change being overriden by a message having the old settings. + //TODO: This is not bullet-proof, as one can have 2 changes between 2 frames... + if (!p.equals(lastMarkerPos)) { + if (p.x == Integer.MIN_VALUE) { + if (buttonMarker.isSelected()) { + buttonMarker.setSelected(false); + checkMarker(null); + } + } else { + if (!buttonMarker.isSelected()) { + buttonMarker.setSelected(true); + checkMarker(p); + } else { + if (!p.equals(marker.getPosition())) { + marker.setPosition(p); + } + } + } + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + Point getStreamMarkerPos() throws IOException { + //System.out.println(server.getInstanceConfig().get("Marker")); + Map pars = server.getProcessingParameters(); + if (pars != null) { + List markerPosition = (List) pars.get("Marker"); + if (markerPosition != null) { + return new Point((Integer) markerPosition.get(0), (Integer) markerPosition.get(1)); + } + return new Point(Integer.MIN_VALUE, Integer.MIN_VALUE); + } + return null; + } + + void clearMarker() { + marker = null; + renderer.setMarker(marker); + } + + void setInstanceConfigValue(String name, Object value) throws IOException { + if (server != null) { + Map map = server.getInstanceConfig(); + map.put(name, value); + server.setInstanceConfig(map); + } + } + + void updateZoom() { + try { + buttonZoomStretch.setSelected(renderer.getMode() == RendererMode.Stretch); + buttonZoomFit.setSelected(renderer.getMode() == RendererMode.Fit); + if (renderer.getMode() == RendererMode.Fixed) { + buttonZoomNormal.setSelected(true); + } else if (renderer.getMode() == RendererMode.Zoom) { + if (renderer.getZoom() == 1) { + buttonZoomNormal.setSelected(true); + } else if (renderer.getZoom() == 0.5) { + buttonZoom05.setSelected(true); + } else if (renderer.getZoom() == 0.25) { + buttonZoom025.setSelected(true); + } else if (renderer.getZoom() == 2.0) { + buttonZoom2.setSelected(true); + } else { + buttonGroup1.clearSelection(); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + boolean updatingColormap; + + void updateColormap() { + updatingColormap = true; + try { + if ((camera != null) && (camera instanceof ColormapSource)) { + ColormapSourceConfig config = ((ColormapSource) camera).getConfig(); + comboColormap.setSelectedItem(config.colormap); + if (config.isDefaultColormap()) { + buttonFullRange.setSelected(true); + } else if (config.colormapAutomatic) { + buttonAutomatic.setSelected(true); + } else { + buttonManual.setSelected(true); + } + btFixColormapRange.setVisible(buttonAutomatic.isSelected()); + spinnerMin.setEnabled(buttonManual.isSelected()); + spinnerMax.setEnabled(buttonManual.isSelected()); + + boolean signed = spinnerBackground.isVisible() && "signed".equals(spinnerBackground.getValue()); + Integer min = signed ? -65535: 0; + if (!min.equals(((SpinnerNumberModel)spinnerMin.getModel()).getMinimum())){ + spinnerMin.setModel(new SpinnerNumberModel(0, min.intValue(), 65535, 1)); + spinnerMax.setModel(new SpinnerNumberModel(255, min.intValue(), 65535, 1)); + if ((Integer)((SpinnerNumberModel)spinnerMin.getModel()).getValue() < min){ + spinnerMin.setValue(min); + } + if ((Integer)((SpinnerNumberModel)spinnerMax.getModel()).getValue() < min){ + spinnerMax.setValue(min); + } + } + + if (!Double.isNaN(config.colormapMin)) { + Integer value = Math.min(Math.max((int) config.colormapMin, min), 65535); + if (spinnerMin.getModel().getValue()!= value){ + spinnerMin.setValue(value); + } + } + if (!Double.isNaN(config.colormapMax)) { + Integer value = Math.min(Math.max((int) config.colormapMax, min), 65535); + if (spinnerMax.getModel().getValue()!= value){ + spinnerMax.setValue(value); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + updatingColormap = false; + } + + void updatePipelineProperties() { + goodRegion = checkGoodRegion.isSelected(); + spinnerBackground.setVisible(checkBackground.isSelected()); + spinnerThreshold.setVisible(checkThreshold.isSelected()); + setGoodRegionOptionsVisible(goodRegion); + slicing = goodRegion && checkSlicing.isSelected(); + setSlicingOptionsVisible(slicing); + updatingServerControls = false; + boolean rotation = checkRotation.isSelected(); + setRotationOptionsVisible(rotation); + boolean averaging = checkAveraging.isSelected(); + setAveragingOptionsVisible(averaging); + } + + boolean updatingServerControls; + + void updatePipelineControls() { + if (server != null) { + updatingServerControls = true; + if (server.isStarted()) { + try { + checkBackground.setSelected(server.isBackgroundSubtractionEnabled()); + setBackgoundControl(server.getBackgroundSubtraction()); + Object bg = server.getBackgroundSubtraction(); + if (bg.equals("signed") || bg.equals("passive")){ + spinnerBackground.setValue(bg); + } else { + spinnerBackground.setValue("normal"); + } + Double threshold = (server.getThreshold()); + checkThreshold.setSelected(threshold != null); + spinnerThreshold.setValue((threshold == null) ? 0 : threshold); + Map gr = (server.getGoodRegion()); + checkGoodRegion.setSelected(gr != null); + if (gr != null) { + spinnerGrThreshold.setValue(((Number) gr.get("threshold")).doubleValue()); + spinnerGrScale.setValue(((Number) gr.get("gfscale")).doubleValue()); + } + Map rotation = server.getRotation(); + checkRotation.setSelected(rotation != null); + if (rotation!=null){ + spinnerRotationAngle.setValue(((Number) rotation.get("angle")).doubleValue()); + spinnerRotationOrder.setValue(((Number) rotation.get("order")).intValue()); + String mode = (String) rotation.get("mode"); + try{ + spinnerRotationConstant.setValue(Double.valueOf(mode)); + spinnerRotationMode.setValue("constant"); + } catch (Exception ex){ + spinnerRotationConstant.setValue(0); + spinnerRotationMode.setValue(mode); + } + } + + Number averaging = (Number) server.getAveraging(); + checkAveraging.setSelected(averaging != null); + if (averaging!=null){ + spinnerAvFrames.setValue(Math.abs(averaging.intValue())); + spinnerAvMode.setValue(averaging.intValue()<0 ? "window" : "single"); + } + + Map slicing = (server.getSlicing()); + checkSlicing.setSelected(slicing != null); + if (slicing != null) { + spinnerSlNumber.setValue(((Number) slicing.get("number_of_slices")).intValue()); + spinnerSlScale.setValue(((Number) slicing.get("scale")).doubleValue()); + spinnerSlOrientation.setValue((String) slicing.get("orientation")); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + updatePipelineProperties(); + } + } + + boolean changedPipelinePars(Map pars1, Map pars2) { + String[] keys = new String[]{"image_background_enable", "image_threshold", "image_good_region", + "threshold", "gfscale", "image_slices", "number_of_slices", "scale", "orientation", "image_background_ok"}; + for (String key : keys) { + Object o1 = pars1.get(key); + Object o2 = pars2.get(key); + if (o1 == null) { + if (o2 != null) { + return true; + } + } else if (!o1.equals(o2)) { + return true; + } + } + return false; + } + + void setBackgoundControl(Object background){ + spinnerBackground.setValue((background.equals("signed") || background.equals("passive")) ? background : "normal"); + } + + void updatePipelineControls(Map pars) { + if (pars != null) { + updatingServerControls = true; + try { + Object background = pars.get("image_background_enable"); + checkBackground.setSelected(!background.equals("") && !background.equals(false) && !background.equals("false")); + setBackgoundControl(background); + + manageOverlayErrorOverlay(Boolean.FALSE.equals(pars.get("image_background_ok"))); + + Double threshold = (Double) (pars.get("image_threshold")); + checkThreshold.setSelected(threshold != null); + spinnerThreshold.setValue((threshold == null) ? 0 : threshold); + Map gr = (Map) pars.get("image_good_region"); + checkGoodRegion.setSelected(gr != null); + if (gr != null) { + Double value = ((Number) gr.get("threshold")).doubleValue(); + spinnerGrThreshold.setValue(value); + Double scale = ((Number) gr.get("gfscale")).doubleValue(); + spinnerGrScale.setValue(scale); + } + Map slicing = (Map) (pars.get("image_slices")); + checkSlicing.setSelected(slicing != null); + if (slicing != null) { + int slices = ((Number) slicing.get("number_of_slices")).intValue(); + spinnerSlNumber.setValue(slices); + double scale = ((Number) slicing.get("scale")).doubleValue(); + spinnerSlScale.setValue(scale); + String orientation = (String) slicing.get("orientation"); + spinnerSlOrientation.setValue(orientation); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + updatePipelineProperties(); + } + } + + void setGoodRegionOptionsVisible(boolean visible) { + spinnerGrThreshold.setVisible(visible); + labelGrThreshold.setVisible(visible); + spinnerGrScale.setVisible(visible); + labelGrScale.setVisible(visible); + panelSlicing.setVisible(visible); + } + + void setSlicingOptionsVisible(boolean visible) { + spinnerSlNumber.setVisible(visible); + labelSlNumber.setVisible(visible); + spinnerSlScale.setVisible(visible); + labelSlScale.setVisible(visible); + spinnerSlOrientation.setVisible(visible); + labelSlOrientation.setVisible(visible); + } + + void setAveragingOptionsVisible(boolean visible) { + labelAvMode.setVisible(visible); + labelAvFrames.setVisible(visible); + spinnerAvMode.setVisible(visible); + spinnerAvFrames.setVisible(visible); + } + + void setRotationOptionsVisible(boolean visible) { + labelAngle.setVisible(visible); + labelOrder.setVisible(visible); + labelMode.setVisible(visible); + labelConstant.setVisible(visible); + spinnerRotationAngle.setVisible(visible); + spinnerRotationOrder.setVisible(visible); + spinnerRotationMode.setVisible(visible); + spinnerRotationConstant.setVisible(visible); + } + + boolean isCameraStopped() { + if (server != null) { + if (!isUrl) { + if (!server.isStarted()) + return true; + } + } + return ((camera == null) || camera.isClosed()); + } + + boolean updatingButtons; + + void updateButtons() { + updatingButtons = true; + try { + boolean active = !isCameraStopped();//(camera != null); + buttonSave.setEnabled(active); + buttonGrabBackground.setEnabled(active); + buttonMarker.setEnabled(active); + buttonProfile.setEnabled(active); + buttonFit.setEnabled(active); + buttonReticle.setEnabled(active && camera.getConfig().isCalibrated()); + buttonStreamData.setEnabled(active && (server != null)); + buttonPause.setEnabled(active); + + if (renderer.isPaused() != buttonPause.isSelected()) { + buttonPause.setSelected(renderer.isPaused()); + buttonPauseActionPerformed(null); + } + if (renderer.getShowReticle() != buttonReticle.isSelected()) { + //buttonReticle.setSelected(renderer.getShowReticle()); + } + if ((renderer.getMarker() == null) && buttonMarker.isSelected()) { + buttonMarker.setSelected(false); + } + buttonSave.setSelected(renderer.isSnapshotDialogVisible()); + + } finally { + updatingButtons = false; + } + } + + Frame lastFrame = null; + Map lastPipelinePars = null; + + State state; + void checkAppState(){ + if (App.isDetached()){ + State state = App.getInstance().getState(); + if (state.isInitialized()){ + state = (camera == null) ? state : camera.getState(); + } + if (state!=this.state){ + App.getInstance().getPropertyChangeSupport().firePropertyChange("appstate", this.state, state); + this.state = state; + } + } + } + + + @Override + protected void onTimer() { + for (Device dev : new Device[]{screen, filter}) { + if (dev != null) { + dev.request(); + } + } + + //textState.setText((camera == null) ? "" : camera.getState().toString());\ + checkAppState(); + + if (App.hasArgument("s")) { + try { + ((Source) getDevice("image")).initialize(); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + updateZoom(); + updateColormap(); + updateButtons(); + checkHistogram.setSelected((histogramDialog != null) && (histogramDialog.isShowing())); + buttonScale.setSelected(renderer.getShowColormapScale()); + try { + Frame frame = getCurrentFrame(); + if (frame != lastFrame) { + lastFrame = frame; + if (frame != null) { + Map pars = getProcessingParameters(frame.cache); + if ((lastPipelinePars == null) || changedPipelinePars(pars, lastPipelinePars)) { + //System.out.println("Update pipeline: " + pars); + lastPipelinePars = pars; + updatePipelineControls(pars); + } + if (panelPulse.isVisible()){ + Object pulse = null; + try{ + pulse = frame.cache.getValue("pulse"); + } catch (Exception ex) { + } + textPulse.setText((pulse==null) ? "" : Str.toString(pulse)); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + Pen penFit = new Pen(new Color(192, 105, 0), 0); + Pen penCross = new Pen(new Color(192, 105, 0), 0); + Pen penSlices = new Pen(Color.CYAN.darker(), 1); + + Frame getCurrentFrame() { + if ((imageBufferLenght > 1) && (renderer.isPaused())) { + int index = ((int) pauseSelection.getValue()) - 1; + synchronized (imageBuffer) { + return ((index>=0) && (index < imageBuffer.size())) ? imageBuffer.get(index) : null; + } + } + return currentFrame; + } + + Frame getFrame(Data data) { + synchronized (imageBuffer) { + for (Frame f : imageBuffer) { + if (f.data == data) { + return f; + } + } + } + return null; + } + + void setImageBufferSize(int size) { + if (renderer.isPaused()) { + throw new RuntimeException("Cannot change buffer size whn paused"); + } + synchronized (imageBuffer) { + imageBufferLenght = size; + imageBuffer.clear(); + } + + } + + Overlay[][] getFitOverlays(Data data) { + Overlays.Polyline hgaussian = null; + Overlays.Polyline vgaussian = null; + Overlays.Polyline hprofile = null; + Overlays.Polyline vprofile = null; + Double xMean = null, xSigma = null, xNorm = null, xCom = null, xRms = null; + Double yMean = null, ySigma = null, yNorm = null, yCom = null, yRms = null; + double[] pX = null, pY = null, gX = null, gY = null; + PointDouble[] sliceCenters = null; + if (data != null) { + int height = data.getHeight(); + int width = data.getWidth(); + int profileSize = renderer.getProfileSize(); + ImageData id = null; + if ((useServerStats) && (server != null)) { + try { + id = getFrame(data); + if (id == null) { + return null; + } + xMean = id.x_fit_mean; + xSigma = id.x_fit_standard_deviation; + yMean = id.y_fit_mean; + ySigma = id.y_fit_standard_deviation; + gX = id.x_fit_gauss_function; + gY = id.y_fit_gauss_function; + pX = id.x_profile; + pY = id.y_profile; + xCom = id.x_center_of_mass; + xRms = id.x_rms; + yCom = id.y_center_of_mass; + yRms = id.y_rms; + sliceCenters = id.sliceCenters; + + profileSize /= 4; + if (pX != null) { + int[] xp = Arr.indexesInt(pX.length); + int[] xg = xp; + int[] yp = new int[pX.length]; + int[] yg = new int[pX.length]; + + List l = Arrays.asList((Double[]) Convert.toWrapperArray(pX)); + double minProfile = Collections.min(l); + double maxProfile = Collections.max(l); + double rangeProfile = maxProfile - minProfile; + double minGauss = minProfile; + double rangeGauss = rangeProfile; + //If not good region, range of profile and fit are similar so save this calcultion + if (goodRegion && id.gr_size_x > 0) { + l = Arrays.asList((Double[]) Convert.toWrapperArray(Arrays.copyOfRange(gX, id.gr_pos_x, id.gr_pos_x + id.gr_size_x))); + minGauss = Collections.min(l); + rangeGauss = Collections.max(l) - minGauss; + } + + for (int i = 0; i < xp.length; i++) { + if (gX != null) { + yg[i] = (int) (height - 1 - (((gX[i] - minGauss) / rangeGauss) * profileSize)); + } + yp[i] = (int) (height - 1 - (((pX[i] - minProfile) / rangeProfile) * profileSize)); + } + + if (goodRegion && id.gr_size_x > 0) { + xg = Arrays.copyOfRange(xg, id.gr_pos_x, id.gr_pos_x + id.gr_size_x); + yg = Arrays.copyOfRange(yg, id.gr_pos_x, id.gr_pos_x + id.gr_size_x); + } + + vgaussian = new Overlays.Polyline(penFit, xg, yg); + vprofile = new Overlays.Polyline(renderer.getPenProfile(), xp, yp); + } + + if (pY != null) { + int[] xp = new int[pY.length]; + int[] xg = new int[pY.length]; + int[] yp = Arr.indexesInt(pY.length); + int[] yg = yp; + + List l = Arrays.asList((Double[]) Convert.toWrapperArray(pY)); + double minProfile = Collections.min(l); + double maxProfile = Collections.max(l); + double rangeProfile = maxProfile - minProfile; + double minGauss = minProfile; + double rangeGauss = rangeProfile; + //If not good region, range of profile and fit are similar so save this calcultion + if (goodRegion && id.gr_size_y > 0) { + l = Arrays.asList((Double[]) Convert.toWrapperArray(Arrays.copyOfRange(gY, id.gr_pos_y, id.gr_pos_y + id.gr_size_y))); + minGauss = Collections.min(l); + rangeGauss = Collections.max(l) - minGauss; + } + + for (int i = 0; i < xp.length; i++) { + if (gY != null) { + xg[i] = (int) (((gY[i] - minGauss) / rangeGauss) * profileSize); + } + xp[i] = (int) (((pY[i] - minProfile) / rangeProfile) * profileSize); + } + + if (goodRegion && id.gr_size_y > 0) { + xg = Arrays.copyOfRange(xg, id.gr_pos_y, id.gr_pos_y + id.gr_size_y); + yg = Arrays.copyOfRange(yg, id.gr_pos_y, id.gr_pos_y + id.gr_size_y); + } + hgaussian = new Overlays.Polyline(penFit, xg, yg); + hprofile = new Overlays.Polyline(renderer.getPenProfile(), xp, yp); + } + } catch (Exception ex) { + System.err.println(ex.getMessage()); + return null; + } + } else { + ArrayProperties properties = data.getProperties(); + double maxPlot = properties.max; + double minPlot = properties.min; + double rangePlot = maxPlot - minPlot; + + if (rangePlot <= 0) { + return null; + } + if (renderer.getCalibration() != null) { + try { + double[] sum = data.integrateVertically(true); + double[] saux = new double[sum.length]; + int[] p = new int[sum.length]; + double[] x_egu = renderer.getCalibration().getAxisX(sum.length); + double[] comRms = getComRms(sum, x_egu); + xCom = comRms[0]; + xRms = comRms[1]; + int[] x = Arr.indexesInt(sum.length); + DescriptiveStatistics stats = new DescriptiveStatistics(sum); + double min = stats.getMin(); + for (int i = 0; i < sum.length; i++) { + saux[i] = sum[i] - min; + } + if (showFit) { + double[] gaussian = fitGaussian(saux, x); + if (gaussian != null) { + if ((gaussian[2] < sum.length * 0.45) + && (gaussian[2] > 2) + && (gaussian[0] > min * 0.03)) { + xNorm = gaussian[0]; + xMean = gaussian[1]; + xSigma = gaussian[2]; + double[] fit = getFitFunction(gaussian, x); + int[] y = new int[x.length]; + for (int i = 0; i < x.length; i++) { + y[i] = (int) (height - 1 - ((((fit[i] + min) / height - minPlot) / rangePlot) * profileSize)); + } + vgaussian = new Overlays.Polyline(penFit, x, y); + } + } + } + if (showProfile) { + for (int i = 0; i < x.length; i++) { + p[i] = (int) (height - 1 - (((sum[i] / height - minPlot) / rangePlot) * profileSize)); + } + vprofile = new Overlays.Polyline(renderer.getPenProfile(), x, p); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + + try { + double[] sum = data.integrateHorizontally(true); + double[] saux = new double[sum.length]; + int[] p = new int[sum.length]; + double[] y_egu = renderer.getCalibration().getAxisY(sum.length); + double[] comRms = getComRms(sum, y_egu); + yCom = comRms[0]; + yRms = comRms[1]; + int[] x = Arr.indexesInt(sum.length); + DescriptiveStatistics stats = new DescriptiveStatistics(sum); + double min = stats.getMin(); + for (int i = 0; i < sum.length; i++) { + saux[i] = sum[i] - min; + } + + if (showFit) { + double[] gaussian = fitGaussian(saux, x); + if (gaussian != null) { + //Only aknowledge beam fully inside the image and peak over 3% of min + if ((gaussian[2] < sum.length * 0.45) + && (gaussian[2] > 2) + && (gaussian[0] > min * 0.03)) { + yNorm = gaussian[0]; + yMean = gaussian[1]; + ySigma = gaussian[2]; + double[] fit = getFitFunction(gaussian, x); + + int[] y = new int[x.length]; + for (int i = 0; i < x.length; i++) { + y[i] = (int) ((((fit[i] + min) / width - minPlot) / rangePlot) * profileSize); + } + hgaussian = new Overlays.Polyline(penFit, y, x); + } + } + } + if (showProfile) { + for (int i = 0; i < x.length; i++) { + p[i] = (int) (((sum[i] / width - minPlot) / rangePlot) * profileSize); + } + hprofile = new Overlays.Polyline(renderer.getPenProfile(), p, x); + } + + } catch (Exception ex) { + ex.printStackTrace(); + } + if (xSigma != null) { + xSigma *= renderer.getCalibration().getScaleX(); + } + if (ySigma != null) { + ySigma *= renderer.getCalibration().getScaleY(); + } + if (xMean != null) { + xMean = data.getX((int) Math.round(xMean)); + } + if (yMean != null) { + yMean = data.getY((int) Math.round(yMean)); + } + } + } + final String units = (renderer.getCalibration() != null) ? "\u00B5m" : "px"; + final String fmt = "%7.1f" + units; + Overlays.Text textCom = null; + Overlay[] pOv = null, fOv = null; + Font fontInfoText = new Font(Font.MONOSPACED, 0, 14); + Point textPosition = new Point(12, 20); + if (showProfile) { + if ((xCom != null) && (yCom != null)) { + String text = String.format("com x: m=" + fmt + " \u03C3=" + fmt + "\ncom y: m=" + fmt + " \u03C3=" + fmt, xCom, xRms, yCom, yRms); + textCom = new Overlays.Text(renderer.getPenProfile(), text, fontInfoText, textPosition); + textCom.setFixed(true); + textCom.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_LEFT); + } + pOv = new Overlay[]{hprofile, vprofile, textCom}; + textPosition = new Point(textPosition.x, textPosition.y + 34); + } + if (showFit) { + Overlays.Crosshairs cross = null; + Overlays.Text textFit = null; + if ((xMean != null) && (yMean != null)) { + String text = String.format("fit x: m=" + fmt + " \u03C3=" + fmt + "\nfit y: m=" + fmt + " \u03C3=" + fmt, xMean, xSigma, yMean, ySigma); + textFit = new Overlays.Text(penFit, text, fontInfoText, textPosition); + textFit.setFixed(true); + textFit.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_LEFT); + Point center = new Point(xMean.intValue(), yMean.intValue()); + if (renderer.getCalibration() != null) { + center = renderer.getCalibration().convertToImagePosition(new PointDouble(xMean, yMean)); + xSigma /= renderer.getCalibration().getScaleX(); + ySigma /= renderer.getCalibration().getScaleY(); + } + cross = new Overlays.Crosshairs(penCross, center, new Dimension(Math.abs(2 * xSigma.intValue()), 2 * Math.abs(ySigma.intValue()))); + } + textPosition = new Point(textPosition.x, textPosition.y + 34); + fOv = new Overlay[]{hgaussian, vgaussian, cross, textFit}; + + if (goodRegion && (id != null)) { + try { + double[] x = id.gr_x_axis; + double[] y = id.gr_y_axis; + Overlays.Rect goodRegionOv = new Overlays.Rect(new Pen(penFit.getColor(), 0, Pen.LineStyle.dotted)); + goodRegionOv.setCalibration(renderer.getCalibration()); + goodRegionOv.setPosition(new Point(id.gr_pos_x, id.gr_pos_y)); + goodRegionOv.setSize(new Dimension(id.gr_x_axis.length, id.gr_y_axis.length)); + fOv = Arr.append(fOv, goodRegionOv); + + if (slicing) { + if (sliceCenters != null) { + for (PointDouble sliceCenter : sliceCenters) { + Overlays.Crosshairs center = new Overlays.Crosshairs(penSlices); + center.setCalibration(renderer.getCalibration()); + center.setAbsolutePosition(sliceCenter); + center.setSize(new Dimension(10, 10)); + fOv = Arr.append(fOv, center); + } + if (sliceCenters.length > 1) { + double[] fit = fitPolynomial(sliceCenters, 1); + double angle = Math.toDegrees(Math.atan(fit[1])); + Overlays.Text text = new Overlays.Text(penSlices, String.format("slice: \u03B8= %5.1fdeg", angle), fontInfoText, textPosition); + text.setFixed(true); + text.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_LEFT); + fOv = Arr.append(fOv, text); + } + } + } + } catch (Exception ex) { + } + } + + } + return new Overlay[][]{pOv, fOv}; + } + return null; + } + + class UserOverlay { + + String name; + Overlay obj; + String[] channels; + } + ArrayList userOverlayConfig; + + void parseUserOverlays() { + Properties userOverlays = new Properties(); + userOverlayConfig = new ArrayList<>(); + if (userOverlaysConfigFile != null) { + try { + try (FileInputStream in = new FileInputStream(getContext().getSetup().expandPath(userOverlaysConfigFile))) { + userOverlays.load(in); + + for (String name : userOverlays.stringPropertyNames()) { + String val = userOverlays.getProperty(name); + try { + UserOverlay uo = new UserOverlay(); + uo.name = name; + String type = val.substring(0, val.indexOf("(")).trim(); + String pars = val.substring(val.indexOf("(") + 1, val.lastIndexOf(")")).trim(); + String[] tokens = pars.split(","); + for (int i = 0; i < tokens.length; i++) { + tokens[i] = tokens[i].trim(); + } + Color color = Color.GRAY; + try { + color = (Color) Color.class.getField(tokens[tokens.length - 1].toUpperCase()).get(null); + } catch (Exception ex) { + } + Pen pen = new Pen(color); + try { + String[] penTokens = tokens[tokens.length - 1].split(":"); + color = (Color) Color.class.getField(penTokens[0].toUpperCase()).get(null); + int width = Integer.valueOf(penTokens[1]); + Pen.LineStyle style = Pen.LineStyle.valueOf(penTokens[2]); + pen = new Pen(color, width, style); + } catch (Exception ex) { + } + switch (type) { + case "Point": + uo.obj = new Overlays.Crosshairs(); + uo.obj.setSize(new Dimension(Integer.valueOf(tokens[2]), Integer.valueOf(tokens[3]))); + break; + case "Line": + uo.obj = new Overlays.Line(); + break; + case "Arrow": + uo.obj = new Overlays.Arrow(); + break; + case "Rect": + uo.obj = new Overlays.Rect(); + break; + case "Ellipse": + uo.obj = new Overlays.Ellipse(); + break; + case "Polyline": + uo.obj = new Overlays.Polyline(); + break; + } + if (type.equals("Polyline") || type.equals("Point")) { + uo.channels = new String[]{tokens[0], tokens[1]}; + } else { + uo.channels = new String[]{tokens[0], tokens[1], tokens[2], tokens[3]}; + } + uo.obj.setPen(pen); + userOverlayConfig.add(uo); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + Overlay[] getUserOverlays(Data data) { + ArrayList ret = new ArrayList<>(); + if (server != null) { + ImageData id = getFrame(data); + for (UserOverlay uo : userOverlayConfig) { + try { + Overlay ov = uo.obj; + //Overlay ov = (Overlay)uo.cls.newInstance(); + ov.setCalibration(renderer.getCalibration()); + boolean valid = false; + if (ov instanceof Overlays.Polyline) { + double[] x = (uo.channels[0].equals("null")) ? null : id.getDoubleArray(uo.channels[0]); + double[] y = (uo.channels[1].equals("null")) ? null : id.getDoubleArray(uo.channels[1]); + if ((x != null) || (y != null)) { + if (x == null) { + x = (renderer.getCalibration() == null) ? Arr.indexesDouble(y.length) : renderer.getCalibration().getAxisX(y.length); + } + if (y == null) { + y = (renderer.getCalibration() == null) ? Arr.indexesDouble(x.length) : renderer.getCalibration().getAxisY(x.length); + } + ((Overlays.Polyline) ov).updateAbsolute(x, y); + valid = true; + } + } else { + Double x = id.getDouble(uo.channels[0]); + Double y = id.getDouble(uo.channels[1]); + if ((x != null) && (y != null)) { + PointDouble position = new PointDouble(x, y); + ov.setAbsolutePosition(position); + if (!(ov instanceof Overlays.Crosshairs)) { + Double x2 = id.getDouble(uo.channels[2]); + Double y2 = id.getDouble(uo.channels[3]); + if ((x != null) && (y != null)) { + DimensionDouble size = new DimensionDouble(x2 - position.x, y2 - position.y); + ov.setAbsoluteSize(size); + valid = true; + } + } else { + valid = true; + } + } + } + if (valid) { + ret.add(ov); + } + } catch (Exception ex) { + //ex.printStackTrace(); + } + } + } + return ret.toArray(new Overlay[0]); + } + + double[] getComRms(double[] arr, double[] x) { + if (arr != null) { + double xmd = 0; + double xmd2 = 0; + double total = 0; + for (int i = 0; i < arr.length; i++) { + double v = (arr[i] * x[i]); + xmd += v; + xmd2 += (v * x[i]); + total += arr[i]; + } + if (total > 0) { + double com = xmd / total; + double com2 = xmd2 / total; + double rms = Math.sqrt(Math.abs(com2 - com * com)); + return new double[]{com, rms}; + } + } + return new double[]{Double.NaN, Double.NaN}; + } + + double[] fitGaussianScript(int[] y, int[] x) { + ScriptManager sm = Context.getInstance().getScriptManager(); + ArrayProperties pY = ArrayProperties.get(y); + sm.setVar("y", y); + sm.setVar("x", x); + InterpreterResult r = sm.eval("r = fit_gaussians(y, x, [" + pY.maxIndex + ",])"); + if (r.exception != null) { + r.exception.printStackTrace(); + } else { + List ret = (List) sm.getVar("r"); + if ((ret != null) && (ret.size() == 1) && (ret.get(0) instanceof List) && (((List) (ret.get(0))).size() == 3)) { + double norm = (Double) ((List) ret.get(0)).get(0); + double mean = (Double) ((List) ret.get(0)).get(1); + double sigma = (Double) ((List) ret.get(0)).get(2); + return new double[]{norm, mean, sigma}; + } + } + return null; + } + + double[] fitGaussian(double[] y, int[] x) { + try { + ArrayProperties pY = ArrayProperties.get(y); + GaussianCurveFitter fitter = GaussianCurveFitter.create().withStartPoint(new double[]{(pY.max - pY.min) / 2, x[pY.maxIndex], 1.0}).withMaxIterations(1000); + ArrayList values = new ArrayList<>(); + for (int i = 0; i < y.length; i++) { + values.add(new WeightedObservedPoint(1.0, x[i], y[i])); + } + return fitter.fit(values); + } catch (Exception ex) { + return null; + } + + } + + double[] fitPolynomial(PointDouble[] points, int order) { + double[] y = new double[points.length]; + double[] x = new double[points.length]; + for (int i = 0; i < points.length; i++) { + x[i] = points[i].x; + y[i] = points[i].y; + } + return fitPolynomial(y, x, order); + } + + double[] fitPolynomial(double[] y, double[] x, int order) { + try { + ArrayProperties pY = ArrayProperties.get(y); + PolynomialCurveFitter fitter = PolynomialCurveFitter.create(order).withMaxIterations(1000); + ArrayList values = new ArrayList<>(); + for (int i = 0; i < y.length; i++) { + values.add(new WeightedObservedPoint(1.0, x[i], y[i])); + } + return fitter.fit(values); + } catch (Exception ex) { + return null; + } + + } + + double[] getFitFunction(double[] pars, int[] x) { + double[] fit = new double[x.length]; + Gaussian g = new Gaussian(pars[0], pars[1], pars[2]); + for (int i = 0; i < x.length; i++) { + fit[i] = g.value(x[i]); + } + return fit; + } + + void setHistogramVisible(boolean value) { + if (value) { + if ((histogramDialog == null) || (!histogramDialog.isShowing())) { + Histogram histogram = new Histogram(true); + histogram.setRenderer(renderer); + histogramDialog = SwingUtils.showDialog(SwingUtils.getWindow(renderer), "Histogram", null, histogram); + renderer.refresh(); + } + } else { + if (histogramDialog != null) { + histogramDialog.setVisible(false); + histogramDialog = null; + } + } + } + + void setLaserState(int bunch, boolean value) throws Exception { + System.out.println("Setting laser state: " + value + " - bunch" + bunch); + //Epics.putq("SIN-TIMAST-TMA:Beam-Las-Delay-Sel", value ? 0 : 1); + if ((bunch<=0) || (bunch==1)){ + Epics.putq("SIN-TIMAST-TMA:Bunch-1-OnDelay-Sel", value ? 0 : 1); + } + if ((bunch<=0) || (bunch==2)){ + Epics.putq("SIN-TIMAST-TMA:Bunch-2-OnDelay-Sel", value ? 0 : 1); + } + + Epics.putq("SIN-TIMAST-TMA:Beam-Apply-Cmd.PROC", 1); + Thread.sleep(3000); + } + + boolean getLaserState(int bunch) throws Exception { + //return (Epics.get("SIN-TIMAST-TMA:Beam-Las-Delay-Sel", Integer.class) == 0); + try{ + if (bunch<=0){ + return getLaserState(1) && getLaserState(2); + } + if (bunch==2){ + return (Epics.get("SWISSFEL-STATUS:Bunch-2-OnDelay-Sel", Integer.class) == 0); + } + return (Epics.get("SWISSFEL-STATUS:Bunch-1-OnDelay-Sel", Integer.class) == 0); + } catch (Exception ex){ + return false; + } + } + + void elog(String logbook, String title, String message, String[] attachments) throws Exception { + String domain = ""; + String category = "Info"; + String entry = ""; + StringBuffer cmd = new StringBuffer(); + + cmd.append("G_CS_ELOG_add -l \"").append(logbook).append("\" "); + cmd.append("-a \"Author=ScreenPanel\" "); + cmd.append("-a \"Type=pshell\" "); + cmd.append("-a \"Entry=").append(entry).append("\" "); + cmd.append("-a \"Title=").append(title).append("\" "); + cmd.append("-a \"Category=").append(category).append("\" "); + cmd.append("-a \"Domain=").append(domain).append("\" "); + for (String attachment : attachments) { + cmd.append("-f \"").append(attachment).append("\" "); + } + cmd.append("-n 1 "); + cmd.append("\"").append(message).append("\" "); + System.out.println(cmd.toString()); + + final Process process = Runtime.getRuntime().exec(new String[]{"bash", "-c", cmd.toString()}); + new Thread(() -> { + try { + process.waitFor(); + int bytes = process.getInputStream().available(); + byte[] arr = new byte[bytes]; + process.getInputStream().read(arr, 0, bytes); + System.out.println(new String(arr)); + bytes = process.getErrorStream().available(); + arr = new byte[bytes]; + process.getErrorStream().read(arr, 0, bytes); + System.err.println(new String(arr)); + } catch (Exception ex) { + System.err.println(ex); + } + }).start(); + } + + void centralizeRenderer() { + Point center = null; + Dimension size = renderer.getImageSize(); + double zoom = (renderer.getMode() == RendererMode.Fixed) ? 1.0 : renderer.getZoom(); + if (renderer.getCalibration() != null) { + center = renderer.getCalibration().getCenter(); + } else if (size != null) { + center = new Point(size.width / 2, size.height / 2); + } + if (center != null) { + Point topleft = new Point(Math.max((int) (center.x - renderer.getWidth() / 2 / zoom), 0), + Math.max((int) (center.y - renderer.getHeight() / 2 / zoom), 0)); + renderer.setViewPosition(topleft); + } + } + + void updatePause() { + int index = ((int) pauseSelection.getValue()) - 1; + synchronized (imageBuffer) { + if (index < imageBuffer.size()) { + Data data = imageBuffer.get(index).data; + long pid = imageBuffer.get(index).cache.getPulseId(); + BufferedImage image = camera.generateImage(data); + renderer.setImage(renderer.getOrigin(), image, data); + + String text = "PID: " + pid; + if (imagePauseOverlay == null) { + Font font = new Font("Verdana", Font.PLAIN, 12); + Dimension d = SwingUtils.getTextSize(text, renderer.getFontMetrics(font)); + imagePauseOverlay = new Text(renderer.getPenErrorText(), "", font, new Point(-20 - d.width, 42)); + imagePauseOverlay.setFixed(true); + imagePauseOverlay.setAnchor(Overlay.ANCHOR_VIEWPORT_OR_IMAGE_TOP_RIGHT); + renderer.addOverlay(imagePauseOverlay); + } + imagePauseOverlay.update(text); + manageFit(image, data); + manageUserOverlays(image, data); + } + } + } + + void removePauseOverlay() { + renderer.removeOverlay(imagePauseOverlay); + imagePauseOverlay = null; + } + + void saveSnapshot() throws Exception { + boolean paused = isPaused(); + try{ + if (!paused){ + setPaused(true); + } + String snapshotFile = null; + synchronized (imageBuffer) { + Frame frame = getCurrentFrame(); + if (frame == null) { + throw new Exception("No current image"); + } + ArrayList frames = new ArrayList<>(); + frames.add(frame); + this.saveFrames(cameraAlias + "_camera_snapshot", frames); + + //Enforce the same timestamp to data & image files. + snapshotFile = getContext().getExecutionPars().getPath() + ".png"; + //renderer.saveSnapshot(snapshotFile, "png", true); + ImageBuffer.saveImage(SwingUtils.createImage(renderer), snapshotFile, "png"); + } + + JPanel panel = new JPanel(); + GridBagLayout layout = new GridBagLayout(); + layout.columnWidths = new int[]{0, 180}; //Minimum width + layout.rowHeights = new int[]{30, 30, 30}; //Minimum height + panel.setLayout(layout); + JComboBox comboLogbook = new JComboBox(new String[]{"SwissFEL commissioning data", "SwissFEL commissioning"}); + JTextField textComment = new JTextField(); + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + panel.add(new JLabel("Data file:"), c); + c.gridy = 1; + panel.add(new JLabel("Logbook:"), c); + c.gridy = 2; + panel.add(new JLabel("Comment:"), c); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + panel.add(textComment, c); + c.gridy = 1; + panel.add(comboLogbook, c); + c.gridy = 0; + panel.add(new JLabel(getContext().getExecutionPars().getPath()), c); + + if (SwingUtils.showOption(getTopLevel(), "Success", panel, OptionType.OkCancel) == OptionResult.Yes) { + StringBuilder message = new StringBuilder(); + message.append("Camera: ").append(cameraAlias).append(" ("). + append((server != null) ? "server" : "direct").append(")").append("\n"); + message.append("Screen: ").append(String.valueOf(valueScreen.getLabel().getText())).append("\n"); + message.append("Filter: ").append(String.valueOf(valueFilter.getLabel().getText())).append("\n"); + message.append("Data file: ").append(getContext().getExecutionPars().getPath()).append("\n"); + message.append("Comment: ").append(textComment.getText()).append("\n"); + //Add slicing message + if ((fitOv != null) && (fitOv.length > 5) && (fitOv[fitOv.length - 1] instanceof Overlays.Text)) { + Overlays.Text text = (Overlays.Text) fitOv[fitOv.length - 1]; + message.append(text.getText()).append("\n"); + } + elog((String) comboLogbook.getSelectedItem(), "ScreenPanel Snapshot", message.toString(), new String[]{snapshotFile}); + } + } finally{ + if (!paused){ + setPaused(false); + } + } + } + + void saveStack() throws Exception { + synchronized (imageBuffer) { + saveFrames(cameraAlias + "_camera_stack", imageBuffer); + } + SwingUtils.showMessage(getTopLevel(), "Success", "Generated data file:\n" + getContext().getExecutionPars().getPath()); + } + + public List getCameraTypes(String name) { + if (name == null) { + return null; + } + if (App.hasArgument(ARG_ALIAS) && (aliases!=null)){ + name = aliases.getOrDefault(name, name); + } + + List ret = new ArrayList(); + if (groups!=null){ + for (String group: Arr.sort(groups.keySet().toArray(new String[0]))){ + if (groups.get(group).contains(name)){ + ret.add(group); + } + } + } else { + for (String s : new String[]{"LCAM"}) { + if (name.contains(s)) { + ret.add(LASER_TYPE); + } + } + for (String s : new String[]{"DSCR", "DSRM", "DLAC"}) { + if (name.contains(s)) { + ret.add(ELECTRONS_TYPE); + } + } + for (String s : new String[]{"PROF", "PPRM", "PSSS", "PSCR", "PSRD"}) { + if (name.contains(s)) { + ret.add(PHOTONICS_TYPE); + } + } + } + ret.add("Unknown"); + return ret; + } + + public Map getProcessingParameters(StreamValue value) throws IOException { + return (Map) JsonSerializer.decode(value.getValue("processing_parameters").toString(), Map.class); + } + + void saveFrames(String name, ArrayList frames) throws IOException { + ArrayList values = new ArrayList<>(); + for (Frame frame : frames) { + values.add(frame.cache); + } + saveImages(name, values); + } + + class DifferentFormatException extends IOException{ + DifferentFormatException(){ + super("Images in stack have different formats"); + } + } + + boolean hasSameFormat(ArrayList frames){ + if (frames.size()>0){ + StreamValue first = frames.get(0).cache; + int width = ((Number) first.getValue("width")).intValue(); + int height = ((Number) first.getValue("height")).intValue(); + Class dataType = first.getValue("image").getClass().getComponentType(); + + for (Frame frame : frames){ + StreamValue image = frame.cache; + if ( (width != ((Number) image.getValue("width")).intValue()) || + (height != ((Number) image.getValue("height")).intValue()) || + (dataType != (image.getValue("image").getClass().getComponentType()))){ + return false; + } + } + } + return true; + } + + void saveImages(String name, ArrayList images) throws IOException { + int depth = images.size(); + if (depth == 0) { + return; + } + + StreamValue first = images.get(0); + String pathRoot = "/camera1/"; + String pathImage = pathRoot + "image"; + String pathPid = pathRoot + "pulse_id"; + String pathTimestampStr = pathRoot + "timestamp_str"; + Map processingPars = getProcessingParameters(first); + String camera = (String) processingPars.get("camera_name"); + List types = getCameraTypes(camera); + + int width = ((Number) first.getValue("width")).intValue(); + int height = ((Number) first.getValue("height")).intValue(); + Class dataType = first.getValue("image").getClass().getComponentType(); + + for (StreamValue image : images){ + if ( (width != ((Number) image.getValue("width")).intValue()) || + (height != ((Number) image.getValue("height")).intValue()) || + (dataType != (image.getValue("image").getClass().getComponentType()))){ + throw new DifferentFormatException(); + } + } + + + getContext().setExecutionPars(name); + DataManager dm = getContext().getDataManager(); + + //Create tables + dm.createDataset(pathImage, dataType, new int[]{depth, height, width}); + dm.createDataset(pathPid, Long.class, new int[]{depth}); + dm.createDataset(pathTimestampStr, String.class, new int[]{depth}); + for (String id : first.getIdentifiers()) { + Object val = first.getValue(id); + if (id.equals("image")) { + } else if (id.equals("processing_parameters")) { + Map pars = getProcessingParameters(first); + for (String key : pars.keySet()) { + if ((pars.get(key) != null) && (pars.get(key) instanceof Map)) { + for (String k : ((Map) pars.get(key)).keySet()) { + Object v = ((Map) pars.get(key)).get(k); + k = key + " " + k; + v = (v == null) ? "" : v; + try{ + dm.setAttribute(pathImage, k, v); + } catch (Exception ex){ + logger.info("Cannot set attribute " + pathImage + ": "+ k + " = " + v + " - " + ex.getMessage()); + } + + } + } else { + Object value = pars.get(key); + try{ + if (value == null) { + value = ""; + } else if (value instanceof List) { + if (((List) value).size() > 0){ + Class cls = ((List) value).get(0).getClass() ; + if (cls == String.class){ + value = ((List) value).toArray(new String[0]); + } else { + value = Convert.toPrimitiveArray(value, cls); + } + } else { + value = ""; + } + } + dm.setAttribute(pathImage, key, value); + } catch (Exception ex){ + logger.info("Cannot set attribute " + pathImage + ": "+ key + " = " + value + " - " + ex.getMessage()); + } + } + } + } else if (val.getClass().isArray()) { + dm.createDataset(pathRoot + id, Double.class, new int[]{depth, Array.getLength(val)}); + } else { + dm.createDataset(pathRoot + id, val.getClass(), new int[]{depth}); + } + } + + //Add metadata + dm.setAttribute(pathRoot, "Camera", camera); + dm.setAttribute(pathRoot, "Images", depth); + dm.setAttribute(pathRoot, "Interval", -1); + dm.setAttribute(pathRoot, "Type", types.toArray(new String[0])); + if (types.contains(ELECTRONS_TYPE)) { + dm.setAttribute(pathRoot, "Screen", String.valueOf(valueScreen.getLabel().getText())); + dm.setAttribute(pathRoot, "Filter", String.valueOf(valueFilter.getLabel().getText())); + } + + //Save data + for (int index = 0; index < depth; index++) { + StreamValue streamValue = images.get(index); + dm.setItem(pathImage, streamValue.getValue("image"), new long[]{index, 0, 0}, new int[]{1, height, width}); + dm.setItem(pathPid, streamValue.getPulseId(), index); + dm.setItem(pathTimestampStr, Chrono.getTimeStr(streamValue.getTimestamp(), "YYYY-MM-dd HH:mm:ss.SSS"), index); + + for (String id : streamValue.getIdentifiers()) { + try{ + Object val = streamValue.getValue(id); + if (id.equals("image")) { + } else if (id.equals("processing_parameters")) { + } else if (val.getClass().isArray()) { + dm.setItem(pathRoot + id, val, index); + } else { + dm.setItem(pathRoot + id, val, index); + } + } catch (Exception ex){ + logger.log(Level.WARNING, null, ex); + System.out.println("Error saving " + id + " index " + index + ": " + ex.getMessage()); + } + } + } + getContext().getDataManager().closeOutput(); + } + + StandardDialog calibrationDialolg; + + void calibrate() throws Exception { + if (server != null) { + server.resetRoi(); + calibrationDialolg = (StandardDialog) getContext().getClassByName("CameraCalibrationDialog").getConstructors()[0].newInstance(new Object[]{getTopLevel(), server.getCurrentCamera(), renderer}); + SwingUtils.centerComponent(getTopLevel(), calibrationDialolg); + calibrationDialolg.setVisible(true); + calibrationDialolg.setListener(new StandardDialogListener() { + @Override + public void onWindowOpened(StandardDialog dlg) { + } + + @Override + public void onWindowClosed(StandardDialog dlg, boolean accepted) { + if (accepted) { + //comboCamerasActionPerformed(null); + } + } + }); + } + } + + ImageIcon getIcon(String name) { + ImageIcon ret = null; + try { + //Path path = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().getPath(),"resources", name + ".png"); + String dir = getClass().getProtectionDomain().getCodeSource().getLocation().getPath() + "resources/"; + if (new File(dir + name + ".png").exists()) { + ret = new javax.swing.ImageIcon(dir + name + ".png"); + } else { + ret = new ImageIcon(ch.psi.pshell.ui.App.class.getResource("/ch/psi/pshell/ui/" + name + ".png")); + if (MainFrame.isDark()) { + try { + ret = new ImageIcon(ch.psi.pshell.ui.App.class.getResource("/ch/psi/pshell/ui/dark/" + name + ".png")); + } catch (Exception e) { + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return ret; + } + + String getIconName(JButton button) { + String ret = button.getIcon().toString(); + if (ret.indexOf(".") > 0) { + ret = ret.substring(0, ret.indexOf(".")); + } + return ret; + } + + void setPaused(boolean paused){ + removePauseOverlay(); + if (camera != null) { + synchronized (imageBuffer) { + if (paused) { + renderer.pause(); + panelCameraSelection.setVisible(false); + pauseSelection.setVisible(true); + if (imageBuffer.size() > 0) { + pauseSelection.setEnabled(true); + pauseSelection.setMaxValue(imageBuffer.size()); + pauseSelection.setValue(imageBuffer.size()); + updatePause(); + } else { + pauseSelection.setEnabled(false); + pauseSelection.setMaxValue(1); + pauseSelection.setValue(1); + } + } else { + imageBuffer.clear(); + renderer.resume(); + //renderer.clear(); + pauseSelection.setVisible(false); + panelCameraSelection.setVisible(true); + } + } + } + } + + boolean isPaused(){ + return renderer.isPaused(); + } + + //////// + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonGroup1 = new javax.swing.ButtonGroup(); + buttonGroup2 = new javax.swing.ButtonGroup(); + buttonGroup3 = new javax.swing.ButtonGroup(); + buttonGroup4 = new javax.swing.ButtonGroup(); + jProgressBar1 = new javax.swing.JProgressBar(); + sidePanel = new javax.swing.JPanel(); + panelZoom = new javax.swing.JPanel(); + buttonZoomFit = new javax.swing.JRadioButton(); + buttonZoomStretch = new javax.swing.JRadioButton(); + buttonZoomNormal = new javax.swing.JRadioButton(); + buttonZoom025 = new javax.swing.JRadioButton(); + buttonZoom05 = new javax.swing.JRadioButton(); + buttonZoom2 = new javax.swing.JRadioButton(); + panelColormap = new javax.swing.JPanel(); + checkHistogram = new javax.swing.JCheckBox(); + comboColormap = new javax.swing.JComboBox(); + jLabel3 = new javax.swing.JLabel(); + jLabel4 = new javax.swing.JLabel(); + buttonFullRange = new javax.swing.JRadioButton(); + buttonManual = new javax.swing.JRadioButton(); + buttonAutomatic = new javax.swing.JRadioButton(); + labelMin = new javax.swing.JLabel(); + spinnerMin = new javax.swing.JSpinner(); + spinnerMax = new javax.swing.JSpinner(); + labelMax = new javax.swing.JLabel(); + btFixColormapRange = new javax.swing.JButton(); + panelScreen = new javax.swing.JPanel(); + valueScreen = new ch.psi.pshell.swing.DeviceValuePanel(); + comboScreen = new javax.swing.JComboBox(); + panelFilter = new javax.swing.JPanel(); + valueFilter = new ch.psi.pshell.swing.DeviceValuePanel(); + comboFilter = new javax.swing.JComboBox(); + panelPipeline = new javax.swing.JPanel(); + checkThreshold = new javax.swing.JCheckBox(); + spinnerThreshold = new javax.swing.JSpinner(); + checkBackground = new javax.swing.JCheckBox(); + checkGoodRegion = new javax.swing.JCheckBox(); + spinnerGrScale = new javax.swing.JSpinner(); + spinnerGrThreshold = new javax.swing.JSpinner(); + labelGrThreshold = new javax.swing.JLabel(); + labelGrScale = new javax.swing.JLabel(); + panelSlicing = new javax.swing.JPanel(); + checkSlicing = new javax.swing.JCheckBox(); + labelSlScale = new javax.swing.JLabel(); + spinnerSlScale = new javax.swing.JSpinner(); + labelSlNumber = new javax.swing.JLabel(); + spinnerSlNumber = new javax.swing.JSpinner(); + labelSlOrientation = new javax.swing.JLabel(); + spinnerSlOrientation = new javax.swing.JSpinner(); + checkRotation = new javax.swing.JCheckBox(); + spinnerRotationAngle = new javax.swing.JSpinner(); + spinnerRotationOrder = new javax.swing.JSpinner(); + labelOrder = new javax.swing.JLabel(); + labelMode = new javax.swing.JLabel(); + spinnerRotationMode = new javax.swing.JSpinner(); + labelAngle = new javax.swing.JLabel(); + labelConstant = new javax.swing.JLabel(); + spinnerRotationConstant = new javax.swing.JSpinner(); + checkAveraging = new javax.swing.JCheckBox(); + spinnerAvFrames = new javax.swing.JSpinner(); + labelAvFrames = new javax.swing.JLabel(); + labelAvMode = new javax.swing.JLabel(); + spinnerAvMode = new javax.swing.JSpinner(); + spinnerBackground = new javax.swing.JSpinner(); + panelPulse = new javax.swing.JPanel(); + buttonPulse1 = new javax.swing.JButton(); + buttonPulse2 = new javax.swing.JButton(); + textPulse = new javax.swing.JTextField(); + topPanel = new javax.swing.JPanel(); + toolBar = new javax.swing.JToolBar(); + buttonSidePanel = new javax.swing.JToggleButton(); + buttonStreamData = new javax.swing.JButton(); + buttonSave = new javax.swing.JToggleButton(); + buttonGrabBackground = new javax.swing.JButton(); + buttonPause = new javax.swing.JToggleButton(); + jSeparator6 = new javax.swing.JToolBar.Separator(); + buttonMarker = new javax.swing.JToggleButton(); + buttonProfile = new javax.swing.JToggleButton(); + buttonFit = new javax.swing.JToggleButton(); + buttonReticle = new javax.swing.JToggleButton(); + buttonScale = new javax.swing.JToggleButton(); + buttonTitle = new javax.swing.JToggleButton(); + pauseSelection = new ch.psi.pshell.swing.ValueSelection(); + panelCameraSelection = new javax.swing.JPanel(); + jLabel1 = new javax.swing.JLabel(); + comboCameras = new javax.swing.JComboBox(); + labelType = new javax.swing.JLabel(); + comboType = new javax.swing.JComboBox(); + renderer = new ch.psi.pshell.imaging.Renderer(); + + setPreferredSize(new java.awt.Dimension(873, 600)); + + panelZoom.setBorder(javax.swing.BorderFactory.createTitledBorder("Zoom")); + + buttonGroup1.add(buttonZoomFit); + buttonZoomFit.setText("Fit"); + buttonZoomFit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoomFitActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoomStretch); + buttonZoomStretch.setText("Stretch"); + buttonZoomStretch.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoomStretchActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoomNormal); + buttonZoomNormal.setText("Normal"); + buttonZoomNormal.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoomNormalActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoom025); + buttonZoom025.setText("1/4"); + buttonZoom025.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoom025ActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoom05); + buttonZoom05.setText("1/2"); + buttonZoom05.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoom05ActionPerformed(evt); + } + }); + + buttonGroup1.add(buttonZoom2); + buttonZoom2.setText("2"); + buttonZoom2.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonZoom2ActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelZoomLayout = new javax.swing.GroupLayout(panelZoom); + panelZoom.setLayout(panelZoomLayout); + panelZoomLayout.setHorizontalGroup( + panelZoomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelZoomLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelZoomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonZoomFit) + .addComponent(buttonZoomNormal) + .addComponent(buttonZoomStretch)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelZoomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonZoom025) + .addComponent(buttonZoom05) + .addComponent(buttonZoom2)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + panelZoomLayout.setVerticalGroup( + panelZoomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelZoomLayout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(panelZoomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonZoomNormal) + .addComponent(buttonZoom025)) + .addGap(0, 0, 0) + .addGroup(panelZoomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonZoomFit) + .addComponent(buttonZoom05)) + .addGap(0, 0, 0) + .addGroup(panelZoomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonZoomStretch) + .addComponent(buttonZoom2)) + .addContainerGap()) + ); + + panelColormap.setBorder(javax.swing.BorderFactory.createTitledBorder("Colormap")); + + checkHistogram.setText("Histogram"); + checkHistogram.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkHistogramActionPerformed(evt); + } + }); + + comboColormap.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + onChangeColormap(evt); + } + }); + + jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel3.setText("Type:"); + + jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + jLabel4.setText("Range:"); + + buttonGroup3.add(buttonFullRange); + buttonFullRange.setText("Full"); + buttonFullRange.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + onChangeColormap(evt); + } + }); + + buttonGroup3.add(buttonManual); + buttonManual.setText("Manual"); + buttonManual.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + onChangeColormap(evt); + } + }); + + buttonGroup3.add(buttonAutomatic); + buttonAutomatic.setText("Automatic"); + buttonAutomatic.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + onChangeColormap(evt); + } + }); + + labelMin.setText("Min:"); + + spinnerMin.setModel(new javax.swing.SpinnerNumberModel(0, 0, 65535, 1)); + spinnerMin.setEnabled(false); + spinnerMin.setPreferredSize(new java.awt.Dimension(77, 20)); + spinnerMin.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + onChangeColormapRange(evt); + } + }); + + spinnerMax.setModel(new javax.swing.SpinnerNumberModel(255, 0, 65535, 1)); + spinnerMax.setEnabled(false); + spinnerMax.setPreferredSize(new java.awt.Dimension(77, 20)); + spinnerMax.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + onChangeColormapRange(evt); + } + }); + + labelMax.setText("Max:"); + + btFixColormapRange.setText("Fix"); + btFixColormapRange.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btFixColormapRangeActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelColormapLayout = new javax.swing.GroupLayout(panelColormap); + panelColormap.setLayout(panelColormapLayout); + panelColormapLayout.setHorizontalGroup( + panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelColormapLayout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel3) + .addComponent(jLabel4)) + .addGap(4, 4, 4) + .addGroup(panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(buttonAutomatic) + .addComponent(buttonFullRange) + .addComponent(buttonManual) + .addComponent(comboColormap, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelColormapLayout.createSequentialGroup() + .addComponent(labelMax) + .addGap(2, 2, 2) + .addComponent(spinnerMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(checkHistogram, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelColormapLayout.createSequentialGroup() + .addComponent(labelMin) + .addGap(2, 2, 2) + .addGroup(panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(btFixColormapRange, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerMin, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addContainerGap()) + ); + + panelColormapLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btFixColormapRange, spinnerMax, spinnerMin}); + + panelColormapLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {jLabel3, jLabel4}); + + panelColormapLayout.setVerticalGroup( + panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelColormapLayout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(comboColormap, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel3) + .addComponent(checkHistogram)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonAutomatic) + .addComponent(jLabel4) + .addComponent(btFixColormapRange, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) + .addGroup(panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(labelMin) + .addComponent(spinnerMin, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonFullRange)) + .addGroup(panelColormapLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(buttonManual) + .addComponent(labelMax) + .addComponent(spinnerMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + panelScreen.setBorder(javax.swing.BorderFactory.createTitledBorder("Screen")); + + comboScreen.setEnabled(false); + comboScreen.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + comboScreenActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelScreenLayout = new javax.swing.GroupLayout(panelScreen); + panelScreen.setLayout(panelScreenLayout); + panelScreenLayout.setHorizontalGroup( + panelScreenLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelScreenLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelScreenLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(valueScreen, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(comboScreen, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + panelScreenLayout.setVerticalGroup( + panelScreenLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelScreenLayout.createSequentialGroup() + .addGap(4, 4, 4) + .addComponent(comboScreen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(valueScreen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + panelFilter.setBorder(javax.swing.BorderFactory.createTitledBorder("Filter")); + + comboFilter.setEnabled(false); + comboFilter.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + comboFilterActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelFilterLayout = new javax.swing.GroupLayout(panelFilter); + panelFilter.setLayout(panelFilterLayout); + panelFilterLayout.setHorizontalGroup( + panelFilterLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelFilterLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelFilterLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(valueFilter, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(comboFilter, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + panelFilterLayout.setVerticalGroup( + panelFilterLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelFilterLayout.createSequentialGroup() + .addGap(4, 4, 4) + .addComponent(comboFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(valueFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + panelPipeline.setBorder(javax.swing.BorderFactory.createTitledBorder("Pipeline")); + + checkThreshold.setText("Threshold"); + checkThreshold.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkThresholdActionPerformed(evt); + } + }); + + spinnerThreshold.setModel(new javax.swing.SpinnerNumberModel(0.0d, 0.0d, 99999.0d, 1.0d)); + spinnerThreshold.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerThresholdonChange(evt); + } + }); + + checkBackground.setText("Subtract Background"); + checkBackground.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkBackgroundActionPerformed(evt); + } + }); + + checkGoodRegion.setText("Good Region"); + checkGoodRegion.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkGoodRegionActionPerformed(evt); + } + }); + + spinnerGrScale.setModel(new javax.swing.SpinnerNumberModel(3.0d, 0.01d, 99999.0d, 1.0d)); + spinnerGrScale.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerGrThresholdonChange(evt); + } + }); + + spinnerGrThreshold.setModel(new javax.swing.SpinnerNumberModel(0.5d, 0.04d, 1.0d, 0.1d)); + spinnerGrThreshold.setPreferredSize(new java.awt.Dimension(92, 20)); + spinnerGrThreshold.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerGrThresholdonChange(evt); + } + }); + + labelGrThreshold.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelGrThreshold.setText("Threshold:"); + + labelGrScale.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelGrScale.setText("Scale:"); + + checkSlicing.setText("Slicing"); + checkSlicing.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkSlicingActionPerformed(evt); + } + }); + + labelSlScale.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelSlScale.setText("Scale:"); + + spinnerSlScale.setModel(new javax.swing.SpinnerNumberModel(3.0d, 0.01d, 99999.0d, 1.0d)); + spinnerSlScale.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerSlicingChange(evt); + } + }); + + labelSlNumber.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelSlNumber.setText("Slices:"); + + spinnerSlNumber.setModel(new javax.swing.SpinnerNumberModel(2, 0, 1000, 1)); + spinnerSlNumber.setPreferredSize(new java.awt.Dimension(92, 20)); + spinnerSlNumber.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerSlicingChange(evt); + } + }); + + labelSlOrientation.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelSlOrientation.setText("Orientation:"); + + spinnerSlOrientation.setModel(new javax.swing.SpinnerListModel(new String[] {"vertical", "horizontal"})); + spinnerSlOrientation.setPreferredSize(new java.awt.Dimension(92, 20)); + spinnerSlOrientation.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerSlicingChange(evt); + } + }); + + javax.swing.GroupLayout panelSlicingLayout = new javax.swing.GroupLayout(panelSlicing); + panelSlicing.setLayout(panelSlicingLayout); + panelSlicingLayout.setHorizontalGroup( + panelSlicingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelSlicingLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelSlicingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelSlicingLayout.createSequentialGroup() + .addComponent(checkSlicing) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelSlicingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelSlicingLayout.createSequentialGroup() + .addComponent(labelSlNumber) + .addGap(2, 2, 2) + .addComponent(spinnerSlNumber, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelSlicingLayout.createSequentialGroup() + .addComponent(labelSlScale) + .addGap(2, 2, 2) + .addComponent(spinnerSlScale, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelSlicingLayout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(labelSlOrientation) + .addGap(2, 2, 2) + .addComponent(spinnerSlOrientation, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + panelSlicingLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {spinnerSlNumber, spinnerSlOrientation, spinnerSlScale}); + + panelSlicingLayout.setVerticalGroup( + panelSlicingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelSlicingLayout.createSequentialGroup() + .addGroup(panelSlicingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(checkSlicing) + .addGroup(panelSlicingLayout.createSequentialGroup() + .addGroup(panelSlicingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerSlNumber, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelSlNumber)) + .addGap(0, 0, 0) + .addGroup(panelSlicingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerSlScale, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelSlScale)))) + .addGap(0, 0, 0) + .addGroup(panelSlicingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerSlOrientation, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelSlOrientation))) + ); + + panelSlicingLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {spinnerSlNumber, spinnerSlOrientation, spinnerSlScale}); + + checkRotation.setText("Rotation"); + checkRotation.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkRotationActionPerformed(evt); + } + }); + + spinnerRotationAngle.setModel(new javax.swing.SpinnerNumberModel(0.0d, -360.0d, 360.0d, 1.0d)); + spinnerRotationAngle.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerRotationAngleStateChanged(evt); + } + }); + + spinnerRotationOrder.setModel(new javax.swing.SpinnerNumberModel(1, 1, 5, 1)); + spinnerRotationOrder.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerRotationAngleStateChanged(evt); + } + }); + + labelOrder.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelOrder.setText("Order:"); + + labelMode.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelMode.setText("Mode:"); + + spinnerRotationMode.setModel(new javax.swing.SpinnerListModel(new String[] {"constant", "reflect", "nearest", "mirror", "wrap", "ortho"})); + spinnerRotationMode.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerRotationAngleStateChanged(evt); + } + }); + + labelAngle.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelAngle.setText("Angle:"); + + labelConstant.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelConstant.setText("Constant:"); + + spinnerRotationConstant.setModel(new javax.swing.SpinnerNumberModel(0, 0, 99999, 1)); + spinnerRotationConstant.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerRotationAngleStateChanged(evt); + } + }); + + checkAveraging.setText("Averaging"); + checkAveraging.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkAveragingActionPerformed(evt); + } + }); + + spinnerAvFrames.setModel(new javax.swing.SpinnerNumberModel(1, 1, 1000, 1)); + spinnerAvFrames.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerAvFramesStateChanged(evt); + } + }); + + labelAvFrames.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelAvFrames.setText("Frames:"); + + labelAvMode.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + labelAvMode.setText("Mode:"); + + spinnerAvMode.setModel(new javax.swing.SpinnerListModel(new String[] {"single", "window"})); + spinnerAvMode.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerAvModeonChange(evt); + } + }); + + spinnerBackground.setModel(new javax.swing.SpinnerListModel(new String[] {"normal", "signed", "passive"})); + spinnerBackground.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerBackgroundStateChanged(evt); + } + }); + + javax.swing.GroupLayout panelPipelineLayout = new javax.swing.GroupLayout(panelPipeline); + panelPipeline.setLayout(panelPipelineLayout); + panelPipelineLayout.setHorizontalGroup( + panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panelSlicing, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelPipelineLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPipelineLayout.createSequentialGroup() + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPipelineLayout.createSequentialGroup() + .addComponent(checkAveraging) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(labelAvFrames, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(labelAvMode, javax.swing.GroupLayout.Alignment.TRAILING))) + .addGroup(panelPipelineLayout.createSequentialGroup() + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(checkGoodRegion) + .addComponent(checkRotation) + .addComponent(checkThreshold)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(labelOrder, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(labelAngle, javax.swing.GroupLayout.Alignment.TRAILING)) + .addComponent(labelGrScale, javax.swing.GroupLayout.Alignment.TRAILING))) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelPipelineLayout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(labelConstant, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(labelMode, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(labelGrThreshold, javax.swing.GroupLayout.Alignment.TRAILING)))) + .addGap(2, 2, 2)) + .addGroup(panelPipelineLayout.createSequentialGroup() + .addComponent(checkBackground) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(spinnerGrThreshold, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerGrScale, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerThreshold, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerRotationOrder, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerRotationMode, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerRotationAngle, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerRotationConstant, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerAvFrames, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerAvMode, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(spinnerBackground, javax.swing.GroupLayout.Alignment.TRAILING)) + .addContainerGap()) + ); + + panelPipelineLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {spinnerAvFrames, spinnerAvMode, spinnerBackground, spinnerGrScale, spinnerGrThreshold, spinnerRotationAngle, spinnerRotationConstant, spinnerRotationMode, spinnerRotationOrder, spinnerThreshold}); + + panelPipelineLayout.setVerticalGroup( + panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPipelineLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkBackground) + .addComponent(spinnerBackground, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkThreshold) + .addComponent(spinnerThreshold, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(2, 2, 2) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkAveraging) + .addComponent(spinnerAvFrames, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelAvFrames)) + .addGap(2, 2, 2) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerAvMode, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelAvMode)) + .addGap(2, 2, 2) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkRotation) + .addComponent(spinnerRotationAngle, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelAngle)) + .addGap(0, 0, 0) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerRotationOrder, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelOrder)) + .addGap(0, 0, 0) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerRotationMode, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelMode)) + .addGap(0, 0, 0) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerRotationConstant, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelConstant)) + .addGap(2, 2, 2) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(checkGoodRegion) + .addComponent(spinnerGrScale, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelGrScale)) + .addGap(0, 0, 0) + .addGroup(panelPipelineLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spinnerGrThreshold, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelGrThreshold)) + .addGap(2, 2, 2) + .addComponent(panelSlicing, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + panelPipelineLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {spinnerAvFrames, spinnerAvMode, spinnerBackground, spinnerGrScale, spinnerGrThreshold, spinnerRotationAngle, spinnerRotationConstant, spinnerRotationMode, spinnerRotationOrder, spinnerThreshold}); + + panelPulse.setBorder(javax.swing.BorderFactory.createTitledBorder("Pulse")); + + buttonPulse1.setText("Pulse 1"); + buttonPulse1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPulse1ActionPerformed(evt); + } + }); + + buttonPulse2.setText("Pulse 2"); + buttonPulse2.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPulse2ActionPerformed(evt); + } + }); + + textPulse.setEditable(false); + textPulse.setHorizontalAlignment(javax.swing.JTextField.CENTER); + + javax.swing.GroupLayout panelPulseLayout = new javax.swing.GroupLayout(panelPulse); + panelPulse.setLayout(panelPulseLayout); + panelPulseLayout.setHorizontalGroup( + panelPulseLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPulseLayout.createSequentialGroup() + .addContainerGap() + .addComponent(buttonPulse1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonPulse2) + .addGap(18, 18, Short.MAX_VALUE) + .addComponent(textPulse, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + panelPulseLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {buttonPulse1, buttonPulse2}); + + panelPulseLayout.setVerticalGroup( + panelPulseLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPulseLayout.createSequentialGroup() + .addGroup(panelPulseLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonPulse1, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonPulse2, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(textPulse, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + javax.swing.GroupLayout sidePanelLayout = new javax.swing.GroupLayout(sidePanel); + sidePanel.setLayout(sidePanelLayout); + sidePanelLayout.setHorizontalGroup( + sidePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(sidePanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(sidePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(panelZoom, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelColormap, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelPipeline, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelScreen, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelFilter, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelPulse, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + sidePanelLayout.setVerticalGroup( + sidePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(sidePanelLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(panelPipeline, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelZoom, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelColormap, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelScreen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelPulse, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + toolBar.setFloatable(false); + toolBar.setRollover(true); + + buttonSidePanel.setIcon(getIcon("List")); + buttonSidePanel.setSelected(true); + buttonSidePanel.setText(" "); + buttonSidePanel.setToolTipText("Show Side Panel"); + buttonSidePanel.setFocusable(false); + buttonSidePanel.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonSidePanel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonSidePanelActionPerformed(evt); + } + }); + toolBar.add(buttonSidePanel); + + buttonStreamData.setIcon(getIcon("Details")); + buttonStreamData.setText(" "); + buttonStreamData.setToolTipText("Show Data Window"); + buttonStreamData.setFocusable(false); + buttonStreamData.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonStreamData.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonStreamDataActionPerformed(evt); + } + }); + toolBar.add(buttonStreamData); + + buttonSave.setIcon(getIcon("Save")); + buttonSave.setText(" "); + buttonSave.setToolTipText("Save Snapshot"); + buttonSave.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonSave.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonSaveActionPerformed(evt); + } + }); + toolBar.add(buttonSave); + + buttonGrabBackground.setIcon(getIcon("Background")); + buttonGrabBackground.setText(" "); + buttonGrabBackground.setToolTipText("Grab Background"); + buttonGrabBackground.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonGrabBackground.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonGrabBackgroundActionPerformed(evt); + } + }); + toolBar.add(buttonGrabBackground); + + buttonPause.setIcon(getIcon("Pause")); + buttonPause.setText(" "); + buttonPause.setToolTipText("Pause"); + buttonPause.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonPause.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonPauseActionPerformed(evt); + } + }); + toolBar.add(buttonPause); + + jSeparator6.setMaximumSize(new java.awt.Dimension(20, 32767)); + jSeparator6.setPreferredSize(new java.awt.Dimension(20, 0)); + jSeparator6.setRequestFocusEnabled(false); + toolBar.add(jSeparator6); + + buttonMarker.setIcon(getIcon("Marker")); + buttonMarker.setText(" "); + buttonMarker.setToolTipText("Show Marker"); + buttonMarker.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonMarker.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonMarkerActionPerformed(evt); + } + }); + toolBar.add(buttonMarker); + + buttonProfile.setIcon(getIcon("Profile" + + "")); + buttonProfile.setSelected(true); + buttonProfile.setText(" "); + buttonProfile.setToolTipText("Show Image Profile"); + buttonProfile.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonProfile.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonProfileActionPerformed(evt); + } + }); + toolBar.add(buttonProfile); + + buttonFit.setIcon(getIcon("Fit")); + buttonFit.setSelected(true); + buttonFit.setText(" "); + buttonFit.setToolTipText("Show Fit"); + buttonFit.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonFit.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonFitActionPerformed(evt); + } + }); + toolBar.add(buttonFit); + + buttonReticle.setIcon(getIcon("Reticule")); + buttonReticle.setSelected(true); + buttonReticle.setText(" "); + buttonReticle.setToolTipText("Show Reticle"); + buttonReticle.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonReticle.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonReticleActionPerformed(evt); + } + }); + toolBar.add(buttonReticle); + + buttonScale.setIcon(getIcon("Scale")); + buttonScale.setSelected(true); + buttonScale.setText(" "); + buttonScale.setToolTipText("Show Colormap Scale"); + buttonScale.setFocusable(false); + buttonScale.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonScale.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonScaleActionPerformed(evt); + } + }); + toolBar.add(buttonScale); + + buttonTitle.setIcon(getIcon("Title")); + buttonTitle.setSelected(true); + buttonTitle.setText(" "); + buttonTitle.setToolTipText("Show Camera Name"); + buttonTitle.setFocusable(false); + buttonTitle.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + buttonTitle.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonTitleActionPerformed(evt); + } + }); + toolBar.add(buttonTitle); + + pauseSelection.setDecimals(0); + + jLabel1.setText("Camera:"); + + comboCameras.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N + comboCameras.setMaximumRowCount(30); + comboCameras.setMinimumSize(new java.awt.Dimension(127, 27)); + comboCameras.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + comboCamerasActionPerformed(evt); + } + }); + + labelType.setText("Type:"); + + comboType.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N + comboType.setMaximumRowCount(30); + comboType.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "All", "Laser", "Electrons", "Photonics" })); + comboType.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + comboTypeActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelCameraSelectionLayout = new javax.swing.GroupLayout(panelCameraSelection); + panelCameraSelection.setLayout(panelCameraSelectionLayout); + panelCameraSelectionLayout.setHorizontalGroup( + panelCameraSelectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelCameraSelectionLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addGap(0, 0, 0) + .addComponent(comboCameras, javax.swing.GroupLayout.PREFERRED_SIZE, 222, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(labelType) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(comboType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) + ); + panelCameraSelectionLayout.setVerticalGroup( + panelCameraSelectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelCameraSelectionLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addGroup(panelCameraSelectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(comboType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelType) + .addComponent(jLabel1) + .addComponent(comboCameras, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0)) + ); + + javax.swing.GroupLayout topPanelLayout = new javax.swing.GroupLayout(topPanel); + topPanel.setLayout(topPanelLayout); + topPanelLayout.setHorizontalGroup( + topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, topPanelLayout.createSequentialGroup() + .addComponent(toolBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(panelCameraSelection, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(18, 18, 18) + .addComponent(pauseSelection, javax.swing.GroupLayout.PREFERRED_SIZE, 334, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + topPanelLayout.setVerticalGroup( + topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(topPanelLayout.createSequentialGroup() + .addGap(1, 1, 1) + .addGroup(topPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(pauseSelection, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(toolBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(panelCameraSelection, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(sidePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(renderer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(topPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(topPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(sidePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(renderer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + ); + }// //GEN-END:initComponents + + private void buttonZoomFitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoomFitActionPerformed + try { + renderer.setMode(RendererMode.Fit); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonZoomFitActionPerformed + + private void buttonZoomStretchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoomStretchActionPerformed + try { + renderer.setMode(RendererMode.Stretch); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonZoomStretchActionPerformed + + private void buttonZoomNormalActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoomNormalActionPerformed + try { + renderer.setMode(RendererMode.Fixed); + centralizeRenderer(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonZoomNormalActionPerformed + + private void onChangeColormap(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_onChangeColormap + try { + if ((camera != null) && (camera instanceof ColormapSource) && !updatingColormap) { + ColormapSource source = (ColormapSource) camera; + Color colorReticule = new Color(16, 16, 16); + Color colorMarker = new Color(128, 128, 128); + Colormap colormap = (Colormap) comboColormap.getSelectedItem(); + source.getConfig().colormap = (colormap == null) ? Colormap.Flame : colormap; + switch (source.getConfig().colormap) { + case Grayscale: + case Inverted: + case Red: + case Green: + case Blue: + colorReticule = new Color(0, 192, 0); + colorMarker = new Color(64, 255, 64); + break; + case Flame: + colorReticule = new Color(0, 192, 0); + colorMarker = new Color(64, 255, 64); + break; + } + + renderer.setPenReticle(new Pen(colorReticule)); + renderer.setPenProfile(new Pen(colorReticule, 0)); + renderer.setPenMarker(new Pen(colorMarker, 2)); + renderer.setShowReticle(false); + checkReticle(); + source.getConfig().colormapAutomatic = buttonAutomatic.isSelected(); + source.getConfig().colormapMin = buttonFullRange.isSelected() ? Double.NaN : (Integer) spinnerMin.getValue(); + source.getConfig().colormapMax = buttonFullRange.isSelected() ? Double.NaN : (Integer) spinnerMax.getValue(); + try { + source.getConfig().save(); + } catch (Exception ex) { + logger.log(Level.WARNING, null, ex); + } + source.refresh(); + if (buttonPause.isSelected()) { + updatePause(); + } + updateColormap(); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_onChangeColormap + + private void onChangeColormapRange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_onChangeColormapRange + onChangeColormap(null); + }//GEN-LAST:event_onChangeColormapRange + + private void buttonZoom025ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoom025ActionPerformed + renderer.setZoom(0.25); + renderer.setMode(RendererMode.Zoom); + centralizeRenderer(); + }//GEN-LAST:event_buttonZoom025ActionPerformed + + private void buttonZoom05ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoom05ActionPerformed + renderer.setZoom(0.5); + renderer.setMode(RendererMode.Zoom); + centralizeRenderer(); + }//GEN-LAST:event_buttonZoom05ActionPerformed + + private void comboScreenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comboScreenActionPerformed + + comboScreen.setEnabled(false); + new Thread(new Runnable() { + @Override + public void run() { + ChannelInteger setpoint = null; + try { + int index = comboScreen.getSelectedIndex(); + if (index >= 0) { + if (cameraName.contains("DSRM")) { + setpoint = new ChannelInteger(null, cameraName + ":POSITION_SP"); + } else { + setpoint = new ChannelInteger(null, cameraName + ":SET_SCREEN1_POS"); + } + setpoint.initialize(); + Integer readback = setpoint.read(); + if ((readback == null) || (setpoint.read() != index)) { + setpoint.write(index); + } + screen.read(); + } + } catch (Exception ex) { + showException(ex); + } finally { + comboScreen.setEnabled(true); + if (setpoint != null) { + setpoint.close(); + } + } + } + }).start(); + }//GEN-LAST:event_comboScreenActionPerformed + + private void comboFilterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comboFilterActionPerformed + try { + String setpoint = (String) comboFilter.getSelectedItem(); + if (setpoint != null) { + if (!setpoint.equals(filter.read())) { + filter.write(setpoint); + } + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_comboFilterActionPerformed + + private void checkHistogramActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkHistogramActionPerformed + try { + setHistogramVisible(checkHistogram.isSelected()); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_checkHistogramActionPerformed + + private void buttonZoom2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonZoom2ActionPerformed + renderer.setZoom(2.0); + renderer.setMode(RendererMode.Zoom); + centralizeRenderer(); + }//GEN-LAST:event_buttonZoom2ActionPerformed + + private void spinnerThresholdonChange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerThresholdonChange + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + server.setThreshold((Double) spinnerThreshold.getValue()); + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_spinnerThresholdonChange + + private void checkBackgroundActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkBackgroundActionPerformed + if (server != null) { + if (!updatingServerControls) { + try { + if (server.isStarted()) { + spinnerBackground.setVisible(checkBackground.isSelected()); + Object bg_mode = checkBackground.isSelected() ? String.valueOf(spinnerBackground.getValue()) : false; + server.setBackgroundSubtraction(bg_mode.equals("normal") ? true : bg_mode); + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + updatingServerControls = true; + checkBackground.setSelected(false); + updatingServerControls = false; + + } + } + } else { + camera.setBackgroundEnabled(checkBackground.isSelected()); + } + }//GEN-LAST:event_checkBackgroundActionPerformed + + private void checkThresholdActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkThresholdActionPerformed + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + spinnerThreshold.setVisible(checkThreshold.isSelected()); + server.setThreshold(checkThreshold.isSelected() ? (Double) spinnerThreshold.getValue() : null); + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_checkThresholdActionPerformed + + private void checkGoodRegionActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkGoodRegionActionPerformed + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + goodRegion = checkGoodRegion.isSelected(); + setGoodRegionOptionsVisible(goodRegion); + if (goodRegion) { + server.setGoodRegion(((Number) spinnerGrThreshold.getValue()).doubleValue(), ((Number) spinnerGrScale.getValue()).doubleValue()); + } else { + server.setGoodRegion(null); + } + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_checkGoodRegionActionPerformed + + private void spinnerGrThresholdonChange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerGrThresholdonChange + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + server.setGoodRegion((Double) spinnerGrThreshold.getValue(), (Double) spinnerGrScale.getValue()); + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_spinnerGrThresholdonChange + + private void btFixColormapRangeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btFixColormapRangeActionPerformed + try { + updatingColormap = true; + ArrayProperties properties = currentFrame.data.getProperties(); + spinnerMax.setValue(properties.max.intValue()); + spinnerMin.setValue(properties.min.intValue()); + buttonManual.setSelected(true); + } catch (Exception ex) { + showException(ex); + } finally { + updatingColormap = false; + onChangeColormap(null); + } + }//GEN-LAST:event_btFixColormapRangeActionPerformed + + private void checkSlicingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkSlicingActionPerformed + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + slicing = checkSlicing.isSelected(); + setSlicingOptionsVisible(slicing); + if (slicing) { + server.setSlicing((Integer) spinnerSlNumber.getValue(), (Double) spinnerSlScale.getValue(), spinnerSlOrientation.getValue().toString()); + } else { + server.setSlicing(null); + } + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_checkSlicingActionPerformed + + private void spinnerSlicingChange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerSlicingChange + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + server.setSlicing((Integer) spinnerSlNumber.getValue(), (Double) spinnerSlScale.getValue(), spinnerSlOrientation.getValue().toString()); + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_spinnerSlicingChange + + private void buttonReticleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonReticleActionPerformed + try { + checkReticle(); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonReticleActionPerformed + + private void buttonFitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonFitActionPerformed + try { + showFit = buttonFit.isSelected(); + if (showFit) { + renderer.setProfile(Renderer.Profile.None); + } else { + renderer.removeOverlays(fitOv); + fitOv = null; + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonFitActionPerformed + + private void buttonProfileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonProfileActionPerformed + try { + showProfile = buttonProfile.isSelected(); + if (showProfile) { + renderer.setProfile(Renderer.Profile.None); + } else { + renderer.removeOverlays(profileOv); + profileOv = null; + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonProfileActionPerformed + + private void buttonMarkerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonMarkerActionPerformed + try { + checkMarker(null); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonMarkerActionPerformed + + private void buttonPauseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPauseActionPerformed + try { + if (!updatingButtons) { + setPaused(buttonPause.isSelected()); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonPauseActionPerformed + + private void buttonGrabBackgroundActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonGrabBackgroundActionPerformed + try { + if (camera != null) { + boolean laserOn1 = getLaserState(1); + boolean laserOn2 = getLaserState(2); + OptionResult ret = null; + if (laserOn1 || laserOn2) { + if (laserOn1){ + ret = SwingUtils.showOption(getTopLevel(), "Capture Background", "Do you want to put Bunch 1 laser on delay for capturing background?", OptionType.YesNoCancel); + if (ret == OptionResult.No) { + laserOn1 = false; + } + if (ret == OptionResult.Cancel) { + return; + } + } + if (laserOn2){ + ret = SwingUtils.showOption(getTopLevel(), "Capture Background", "Do you want to put Bunch 2 laser on delay for capturing background?", OptionType.YesNoCancel); + if (ret == OptionResult.No) { + laserOn2= false; + } + } + } else { + ret = SwingUtils.showOption(getTopLevel(), "Capture Background", "Do you want to capture background now?", OptionType.OkCancel); + } + + if (ret == OptionResult.Cancel) { + return; + } + + if (laserOn1) { + setLaserState(1,false); + } + if (laserOn2) { + setLaserState(2,false); + } + try { + System.out.println("Grabbing background for: " + cameraAlias); + if (server != null) { + server.captureBackground(5); + } else { + camera.captureBackground(5, 0); + } + } finally { + if (laserOn1) { + setLaserState(1,true); + } + if (laserOn2) { + setLaserState(2,true); + } + } + SwingUtils.showMessage(getTopLevel(), "Success", "Success capturing background", 5000); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonGrabBackgroundActionPerformed + + private void buttonSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonSaveActionPerformed + try { + saveSnapshot(); + } catch (Exception ex) { + logger.log(Level.WARNING, null, ex); + showException(ex); + } + }//GEN-LAST:event_buttonSaveActionPerformed + + private void buttonStreamDataActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonStreamDataActionPerformed + try { + streamPanel = showDevicePanel(server.getStream()); + ((JDialog)streamPanel.getWindow()).setTitle("Stream Data"); + streamPanel.setReadOnly(true); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonStreamDataActionPerformed + + private void buttonSidePanelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonSidePanelActionPerformed + sidePanel.setVisible(buttonSidePanel.isSelected()); + }//GEN-LAST:event_buttonSidePanelActionPerformed + + private void comboTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comboTypeActionPerformed + try { + if (!updatingCameraSelection) { + updateCameraList(); + if ((cameraAlias != null) && (!cameraAlias.equals(comboCameras.getSelectedItem()))) { + setCamera(null); + } + } + + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + updateButtons(); + } + }//GEN-LAST:event_comboTypeActionPerformed + + private void comboCamerasActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comboCamerasActionPerformed + try { + if (!updatingCameraSelection) { + if (!comboCameras.isEnabled()) { + throw new Exception("Invalid state"); + } + comboCameras.setEnabled(false); + comboType.setEnabled(false); + final String cameraName = (String) comboCameras.getSelectedItem(); + new Thread(new Runnable() { + @Override + public void run() { + if (requestCameraListUpdate) { + requestCameraListUpdate = false; + try { + updateCameraList(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + try { + setCamera(cameraName.trim().isEmpty() ? null : cameraName); + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + updateButtons(); + comboCameras.setEnabled(true); + comboType.setEnabled(true); + } + } + }).start(); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_comboCamerasActionPerformed + + private void buttonTitleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonTitleActionPerformed + try { + manageTitleOverlay(); + } catch (Exception ex) { + showException(ex); + } finally { + } + }//GEN-LAST:event_buttonTitleActionPerformed + + private void buttonScaleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonScaleActionPerformed + try { + renderer.setShowColormapScale(buttonScale.isSelected()); + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonScaleActionPerformed + + private void checkRotationActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkRotationActionPerformed + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + boolean rotation = checkRotation.isSelected(); + setRotationOptionsVisible(rotation); + if (rotation) { + spinnerRotationAngleStateChanged(null); + } else { + server.setRotation(null); + } + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_checkRotationActionPerformed + + private void spinnerRotationAngleStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerRotationAngleStateChanged + try { + String mode = String.valueOf(spinnerRotationMode.getValue()); + server.setRotation(((Number) spinnerRotationAngle.getValue()).doubleValue(), + ((Number) spinnerRotationOrder.getValue()).intValue(), + mode.equals("constant") ? String.valueOf(spinnerRotationConstant.getValue()): mode); + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + }//GEN-LAST:event_spinnerRotationAngleStateChanged + + private void checkAveragingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkAveragingActionPerformed + if (!updatingServerControls) { + try { + if ((server != null) && (server.isStarted())) { + setAveragingOptionsVisible(checkAveraging.isSelected()); + Integer av = spinnerAvMode.getValue().equals("window") ? - (Integer)spinnerAvFrames.getValue() : (Integer)spinnerAvFrames.getValue(); + setInstanceConfigValue("averaging", checkAveraging.isSelected() ? av : null); + } + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_checkAveragingActionPerformed + + private void spinnerAvModeonChange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerAvModeonChange + checkAveragingActionPerformed(null); + }//GEN-LAST:event_spinnerAvModeonChange + + private void spinnerAvFramesStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerAvFramesStateChanged + checkAveragingActionPerformed(null); + }//GEN-LAST:event_spinnerAvFramesStateChanged + + private void spinnerBackgroundStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerBackgroundStateChanged + if (!updatingServerControls) { + try { + Object bg_mode = String.valueOf(spinnerBackground.getValue()); + server.setBackgroundSubtraction((bg_mode=="normal") ? true : bg_mode); + } catch (Exception ex) { + showException(ex); + updatePipelineControls(); + } + } + }//GEN-LAST:event_spinnerBackgroundStateChanged + + private void buttonPulse1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPulse1ActionPerformed + try { + if ((server != null) && (server.isStarted())) { + server.setInstanceConfigValue("pulse",1); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonPulse1ActionPerformed + + private void buttonPulse2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPulse2ActionPerformed + try { + if ((server != null) && (server.isStarted())) { + server.setInstanceConfigValue("pulse",2); + } + } catch (Exception ex) { + showException(ex); + } + }//GEN-LAST:event_buttonPulse2ActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btFixColormapRange; + private javax.swing.JRadioButton buttonAutomatic; + private javax.swing.JToggleButton buttonFit; + private javax.swing.JRadioButton buttonFullRange; + private javax.swing.JButton buttonGrabBackground; + private javax.swing.ButtonGroup buttonGroup1; + private javax.swing.ButtonGroup buttonGroup2; + private javax.swing.ButtonGroup buttonGroup3; + private javax.swing.ButtonGroup buttonGroup4; + private javax.swing.JRadioButton buttonManual; + private javax.swing.JToggleButton buttonMarker; + private javax.swing.JToggleButton buttonPause; + private javax.swing.JToggleButton buttonProfile; + private javax.swing.JButton buttonPulse1; + private javax.swing.JButton buttonPulse2; + private javax.swing.JToggleButton buttonReticle; + private javax.swing.JToggleButton buttonSave; + private javax.swing.JToggleButton buttonScale; + private javax.swing.JToggleButton buttonSidePanel; + private javax.swing.JButton buttonStreamData; + private javax.swing.JToggleButton buttonTitle; + private javax.swing.JRadioButton buttonZoom025; + private javax.swing.JRadioButton buttonZoom05; + private javax.swing.JRadioButton buttonZoom2; + private javax.swing.JRadioButton buttonZoomFit; + private javax.swing.JRadioButton buttonZoomNormal; + private javax.swing.JRadioButton buttonZoomStretch; + private javax.swing.JCheckBox checkAveraging; + private javax.swing.JCheckBox checkBackground; + private javax.swing.JCheckBox checkGoodRegion; + private javax.swing.JCheckBox checkHistogram; + private javax.swing.JCheckBox checkRotation; + private javax.swing.JCheckBox checkSlicing; + private javax.swing.JCheckBox checkThreshold; + private javax.swing.JComboBox comboCameras; + private javax.swing.JComboBox comboColormap; + private javax.swing.JComboBox comboFilter; + private javax.swing.JComboBox comboScreen; + private javax.swing.JComboBox comboType; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JProgressBar jProgressBar1; + private javax.swing.JToolBar.Separator jSeparator6; + private javax.swing.JLabel labelAngle; + private javax.swing.JLabel labelAvFrames; + private javax.swing.JLabel labelAvMode; + private javax.swing.JLabel labelConstant; + private javax.swing.JLabel labelGrScale; + private javax.swing.JLabel labelGrThreshold; + private javax.swing.JLabel labelMax; + private javax.swing.JLabel labelMin; + private javax.swing.JLabel labelMode; + private javax.swing.JLabel labelOrder; + private javax.swing.JLabel labelSlNumber; + private javax.swing.JLabel labelSlOrientation; + private javax.swing.JLabel labelSlScale; + private javax.swing.JLabel labelType; + private javax.swing.JPanel panelCameraSelection; + private javax.swing.JPanel panelColormap; + private javax.swing.JPanel panelFilter; + private javax.swing.JPanel panelPipeline; + private javax.swing.JPanel panelPulse; + private javax.swing.JPanel panelScreen; + private javax.swing.JPanel panelSlicing; + private javax.swing.JPanel panelZoom; + private ch.psi.pshell.swing.ValueSelection pauseSelection; + private ch.psi.pshell.imaging.Renderer renderer; + private javax.swing.JPanel sidePanel; + private javax.swing.JSpinner spinnerAvFrames; + private javax.swing.JSpinner spinnerAvMode; + private javax.swing.JSpinner spinnerBackground; + private javax.swing.JSpinner spinnerGrScale; + private javax.swing.JSpinner spinnerGrThreshold; + private javax.swing.JSpinner spinnerMax; + private javax.swing.JSpinner spinnerMin; + private javax.swing.JSpinner spinnerRotationAngle; + private javax.swing.JSpinner spinnerRotationConstant; + private javax.swing.JSpinner spinnerRotationMode; + private javax.swing.JSpinner spinnerRotationOrder; + private javax.swing.JSpinner spinnerSlNumber; + private javax.swing.JSpinner spinnerSlOrientation; + private javax.swing.JSpinner spinnerSlScale; + private javax.swing.JSpinner spinnerThreshold; + private javax.swing.JTextField textPulse; + private javax.swing.JToolBar toolBar; + private javax.swing.JPanel topPanel; + private ch.psi.pshell.swing.DeviceValuePanel valueFilter; + private ch.psi.pshell.swing.DeviceValuePanel valueScreen; + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/ScreenPanel9.form b/plugins/ScreenPanel9.form index 0f11c92..5aba95b 100644 --- a/plugins/ScreenPanel9.form +++ b/plugins/ScreenPanel9.form @@ -1177,7 +1177,6 @@ - diff --git a/plugins/ScreenPanel9.java b/plugins/ScreenPanel9.java index cf7a6e2..0a37a39 100644 --- a/plugins/ScreenPanel9.java +++ b/plugins/ScreenPanel9.java @@ -1320,7 +1320,7 @@ public class ScreenPanel9 extends Panel { updatePipelineControls(); if (renderer.getDevice() == null) { //renderer.setZoom(1.0); - //renderer.setMode(RendererMode.Zoom); + //renderer.setMode(RenderegetDisplayNamerMode.Zoom); errorOverlay = new Text(renderer.getPenErrorText(), ex.toString(), new Font("Verdana", Font.PLAIN, 12), new Point(20, 20)); errorOverlay.setFixed(true); errorOverlay.setAnchor(Overlay.ANCHOR_VIEWPORT_TOP_LEFT); @@ -2832,13 +2832,18 @@ public class ScreenPanel9 extends Panel { dm.setItem(pathTimestampStr, Chrono.getTimeStr(streamValue.getTimestamp(), "YYYY-MM-dd HH:mm:ss.SSS"), index); for (String id : streamValue.getIdentifiers()) { - Object val = streamValue.getValue(id); - if (id.equals("image")) { - } else if (id.equals("processing_parameters")) { - } else if (val.getClass().isArray()) { - dm.setItem(pathRoot + id, val, index); - } else { - dm.setItem(pathRoot + id, val, index); + try{ + Object val = streamValue.getValue(id); + if (id.equals("image")) { + } else if (id.equals("processing_parameters")) { + } else if (val.getClass().isArray()) { + dm.setItem(pathRoot + id, val, index); + } else { + dm.setItem(pathRoot + id, val, index); + } + } catch (Exception ex){ + logger.log(Level.WARNING, null, ex); + System.out.println("Error saving " + id + " index " + index + ": " + ex.getMessage()); } } } @@ -2998,7 +3003,7 @@ public class ScreenPanel9 extends Panel { Frame frame = getCurrentFrame(); int[] sel_rows = (dataTable == null) ? null : dataTable.getSelectedRows(); int[] sel_cols = (dataTable == null) ? null : dataTable.getSelectedColumns(); - List ids = (value == null) ? new ArrayList<>() : new ArrayList(value.getIdentifiers()); + List ids = (value == null) ? new ArrayList<>() : value.getIdentifiers();//### TODO if (ids.size() + 4 != dataTableModel.getRowCount()) { dataTableModel.setNumRows(0); try { diff --git a/plugins/ShiftsIO.java b/plugins/ShiftsIO.java new file mode 100644 index 0000000..9c3daff --- /dev/null +++ b/plugins/ShiftsIO.java @@ -0,0 +1,109 @@ +import ij.io.OpenDialog; +import ij.io.SaveDialog; +import java.util.ArrayList; +import java.io.IOException; +import java.io.File; +import jmatio.types.*; +import jmatio.io.*; +import ij.gui.GenericDialog; +import ij.IJ; +import ij.Prefs; + +public class ShiftsIO { + + public ShiftsIO () {} + + public double[][] load(String filename, String varname) { + if (filename==null){ + String analysis_dir = Prefs.get("peem.analysis_dir", ""); + OpenDialog od = new OpenDialog("Open_shifts .mat file:", analysis_dir, "shifts.mat"); + String dir = od.getDirectory(); + if (null == dir) return null; // dialog was canceled + if (!dir.endsWith(File.separator)) dir += File.separator; + filename = dir + od.getFileName(); + Prefs.set("peem.analysis_dir", dir); + } + return low_load(filename, varname); + } + + private double[][] low_load(String filename, String varname) { + MatFileReader mfr = null; + try { + mfr = new MatFileReader(filename); + } catch (IOException e) { + System.err.println("Caught IOException: " + + e.getMessage()); + } + + if (mfr != null) { + MLDouble array = (MLDouble)mfr.getMLArray(varname); + if (array != null){ + return array.getArray(); + } else { + return null; + } + } else { + return null; + } + } + + public void save(String filename, double[][] shifts, String varname) { + boolean update = false; + if (filename==null){ + String analysis_dir = Prefs.get("peem.analysis_dir", ""); + SaveDialog sd = new SaveDialog("Save_shifts .mat file:", analysis_dir, "shifts.mat", ".mat"); + String dir = sd.getDirectory(); + String fname = sd.getFileName(); + if (null == fname) return; // user canceled dialog + if (!dir.endsWith(File.separator)) dir += File.separator; + Prefs.set("peem.analysis_dir", dir); + + if ((new File(dir + fname)).exists()) { // if file exist, overwrite or update shifts ? + GenericDialog gd = new GenericDialog("Update"); + String[] options = {"overwrite","update"}; + gd.addChoice("Mode of operation with existing file. You want to: ", options, options[0]); + gd.showDialog(); + if (gd.wasCanceled()) return; + if (gd.getNextChoiceIndex() == 1) update = true; + } + filename = dir + fname; + } + double[][] final_shifts; + if (update) { + double[][] prev_shifts = low_load(filename, "directshifts"); + if (prev_shifts == null) { + IJ.error("Can't read the direct shifts in this file"); + return; + } + + if (prev_shifts.length == shifts.length) { + for (int j = 0; j < prev_shifts.length; j++) { + prev_shifts[j][3] += shifts[j][3]; + prev_shifts[j][4] += shifts[j][4]; + } + } else if (shifts.length == 2) {// 2 shifts, like between two slices of different + // sequences + for (int j = 0; j < prev_shifts.length; j++) { + prev_shifts[j][3] += shifts[1][3]; + prev_shifts[j][4] += shifts[1][4]; + } + } else { + IJ.error("Old and new shifts have incompatible length !"); + return; + } + final_shifts = prev_shifts; + } else { + final_shifts = shifts; + } + + MLDouble mlDouble = new MLDouble(varname, final_shifts); + ArrayList list = new ArrayList(); + list.add(mlDouble); + try { + new MatFileWriter(filename, list); + } catch (IOException e) { + System.err.println("Caught IOException: " + + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/plugins/SpinnerLayoutTest.java b/plugins/SpinnerLayoutTest.java index 0eed633..8629629 100644 --- a/plugins/SpinnerLayoutTest.java +++ b/plugins/SpinnerLayoutTest.java @@ -19,7 +19,7 @@ public class SpinnerLayoutTest { super.setLayout(new SpinnerLayout()); } }; - spinner.set + JPanel p = new JPanel(new BorderLayout(5,5)); p.add(new JSpinner(m), BorderLayout.NORTH); p.add(spinner, BorderLayout.SOUTH); diff --git a/plugins/Standard.java b/plugins/Standard.java new file mode 100644 index 0000000..7ab542f --- /dev/null +++ b/plugins/Standard.java @@ -0,0 +1,6 @@ +public class Standard{ + + public void run() { + System.out.println("run"); + } +} diff --git a/plugins/TstProc.form b/plugins/TstProc.form new file mode 100644 index 0000000..ff9cfab --- /dev/null +++ b/plugins/TstProc.form @@ -0,0 +1,28 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/TstProc.java b/plugins/TstProc.java new file mode 100644 index 0000000..21112dc --- /dev/null +++ b/plugins/TstProc.java @@ -0,0 +1,195 @@ + +import ch.psi.pshell.core.JsonSerializer; +import ch.psi.pshell.ui.PanelProcessor; +import ch.psi.pshell.ui.Task; +import ch.psi.utils.State; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +/** + * Template for a processor plugin backed by json files. + */ +public class TstProc extends PanelProcessor { + + public static final String TYPE = "TstProc"; + public static final String FILE_EXTENSION = "json"; + public static final String HOME_PATH = "{home}/" + TYPE; + + File currentFile; + + public TstProc() { + initComponents(); + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + + } + + @Override + public void onStateChange(State state, State former) { + + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + @Override + public void onTaskFinished(Task task) { + } + + @Override + protected void onTimer() { + } + + @Override + protected void onLoaded() { + + } + + @Override + protected void onUnloaded() { + + } + + //Invoked by 'update()' to update components in the event thread + @Override + protected void doUpdate() { + } + + //Processor Configuration + @Override + public String getType() { + return TYPE; + } + + @Override + public String getDescription() { + return getType() + " definition file (*." + FILE_EXTENSION + ")"; + } + + @Override + public String getHomePath() { + return HOME_PATH; + } + + @Override + public String[] getExtensions() { + return new String[]{FILE_EXTENSION}; + } + + @Override + public String getFileName() { + return (currentFile == null) ? null : currentFile.toString(); + } + + @Override + public boolean createFilePanel() { + return true; + } + + @Override + public boolean createMenuNew() { + return true; + } + + @Override + public boolean canStep() { + return false; + } + + @Override + public boolean canPause() { + return false; + } + + @Override + public boolean isTabNameUpdated() { + return false; + } + + @Override + public void saveAs(String fileName) throws IOException { + currentFile = new File(fileName); + Map config = getConfig(); + String json = JsonSerializer.encode(config, true); + Files.write(currentFile.toPath(), json.getBytes()); + } + + @Override + public void open(String fileName) throws IOException { + clear(); + if (fileName != null) { + Path path = Paths.get(fileName); + String json = new String(Files.readAllBytes(path)); + Map config = (Map) JsonSerializer.decode(json, Map.class); + currentFile = path.toFile(); + setConfig(config); + } + } + + //Component update + public void clear() throws IOException { + currentFile=null; + //TODO + } + + Map getConfig() throws IOException { + //TODO + return new HashMap(); + } + + void setConfig(Map config) throws IOException { + //TODO + } + + //Workbench actions + @Override + public void plotDataFile(File file) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public void step() { + } + + @Override + public void pause() { + } + + @Override + public void abort() throws InterruptedException { + super.abort(); + } + + @Override + public void execute() throws Exception { + this.evalAsync("tscan(sin,10,0.2)"); + } + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 449, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 137, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/plugins/resources/.DS_Store b/plugins/resources/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/sc.java b/plugins/sc.java new file mode 100644 index 0000000..832a911 --- /dev/null +++ b/plugins/sc.java @@ -0,0 +1,91 @@ + +import ch.psi.pshell.ui.App; +import ch.psi.pshell.ui.Panel; +import ch.psi.pshell.ui.StripChart; +import ch.psi.utils.State; +import ch.psi.utils.Sys; +import java.io.File; + +/** + * + */ +public class sc extends Panel { + StripChart stripChart; + public sc() { + initComponents(); + + /* + stripChart = new StripChart(this.getTopLevel(),false, null); + this.add(stripChart.getPlotPanel()); + try { + stripChart.open(new File("test.scd")); + stripChart.start(); + } catch (Exception ex) { showException(ex); + } + */ + } + + //Overridable callbacks + @Override + public void onInitialize(int runCount) { + plotPanel.removeAll(); + plotPanel.add(StripChart.getPlotPanel(new File("test.scd"))); + } + + @Override + public void onStateChange(State state, State former) { + + } + + @Override + public void onExecutedFile(String fileName, Object result) { + } + + @Override + protected void onTimer() { + } + + @Override + protected void onLoaded() { + + } + + @Override + protected void onUnloaded() { + + } + + //Invoked by 'update()' to update components in the event thread + @Override + protected void doUpdate() { + } + + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + plotPanel = new javax.swing.JPanel(); + + plotPanel.setLayout(new java.awt.BorderLayout()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(plotPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(plotPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 232, Short.MAX_VALUE) + .addGap(170, 170, 170)) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel plotPanel; + // End of variables declaration//GEN-END:variables +} diff --git a/script/.DS_Store b/script/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ed1d72bb8fe7e38f46dcda903ea2bab359412da6 GIT binary patch literal 30724 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 - - + + 1236.9 1243.9 @@ -22,8 +22,8 @@ - - + + 0.0 @@ -33,23 +33,19 @@ - + - + return c/d - + - + return c/d diff --git a/script/20210614181250_XRD_XRF_otf_200ms_17200eV_XY_tisbe_Cu_HR2um.xml b/script/20210614181250_XRD_XRF_otf_200ms_17200eV_XY_tisbe_Cu_HR2um.xml new file mode 100644 index 0000000..641b014 --- /dev/null +++ b/script/20210614181250_XRD_XRF_otf_200ms_17200eV_XY_tisbe_Cu_HR2um.xml @@ -0,0 +1,171 @@ + + + + tisbe Cu sample, Norway + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -0.15 + 0.52 + 0.002 + 0.2 + 0.005 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -0.0 + 1.0 + 0.002 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/script/52_ParametersAndReturn.py b/script/52_ParametersAndReturn.py new file mode 100644 index 0000000..65145cb --- /dev/null +++ b/script/52_ParametersAndReturn.py @@ -0,0 +1,23 @@ +################################################################################################### +# Setting script parameters and return value +################################################################################################### + + +#Providing a map of global variables +#run ("52_ParametersAndReturn", {"start":10.0, "end":50.0, "step":40}) + +#Providing the locals dictionary +# The parameters are not set as globals, and nor script definitions +#run ("52_ParametersAndReturn", locals={"start":10.0, "end":50.0, "step":40}) + +#Setting sys.argv: +#run ("52_ParametersAndReturn", [10.0, 50.0, 40]) + +#In this case the parameters would be parsed as: +#start = sys.argv[0] +#end = sys.argv[1] +#step = sys.argv[2] + + +ret = lscan(ao1, ai1, start, end, step, 0.1) +set_return(ret[ai1]) \ No newline at end of file diff --git a/script/Correlation/Correlation.py b/script/Correlation/Correlation.py index 7f872fe..b58e49b 100644 --- a/script/Correlation/Correlation.py +++ b/script/Correlation/Correlation.py @@ -2,7 +2,7 @@ import math import sys, traceback from mathutils import fit_polynomial, PolynomialFunction from plotutils import plot_line, plot_function -from ch.psi.pshell.swing.Shell import STDOUT_COLOR +from ch.psi.pshell.swing.Shell import getColorStdout import org.apache.commons.math3.stat.correlation.PearsonsCorrelation as PearsonsCorrelation start_task("outupdate", 0.0, 0.0) @@ -146,7 +146,7 @@ try: if marker is not None: p.removeMarker(marker) marker = p.addMarker(x2+res, p.AxisId.X, s, p.getBackground()) - marker.setLabelPaint(STDOUT_COLOR) + marker.setLabelPaint(getColorStdout()) if linear_fit: #Calculate, print and plot linear fit pars_lin = (a0,a1) = fit_polynomial(ay, ax, 1) diff --git a/script/ManipulatorScan.py b/script/ManipulatorScan.py index 6bc3cff..d34e89b 100755 --- a/script/ManipulatorScan.py +++ b/script/ManipulatorScan.py @@ -8,8 +8,8 @@ STEPS (int or tuple) LATENCY (double) RELATIVE (BOOLEAN) """ - -if get_exec_pars().source == CommandSource.ui: + +if not get_exec_pars().args: MOTOR = motor SENSORS = (det.dataMatrix, scienta.spectrum, scienta.stats[0]) RANGE = (-5.0, 5.0) @@ -17,6 +17,6 @@ if get_exec_pars().source == CommandSource.ui: LATENCY = 0.0 RELATIVE = False - + lscan(MOTOR, SENSORS, RANGE[0], RANGE[1], STEPS, LATENCY, RELATIVE, before_read=trig_scienta) diff --git a/script/ManualScan.py b/script/ManualScan.py new file mode 100644 index 0000000..1f79be2 --- /dev/null +++ b/script/ManualScan.py @@ -0,0 +1,43 @@ +################################################################################################### +#Manual scan: Manually setting positioners and reading back sensors, but still using +#the standard data handling and plotting of built-in scans. +################################################################################################### + + +mu.setSpeed(10.0) + +pos1 = mu +pos2 = out +det1 =sin +det2 = arr +det3 = cm1#ri1 + + + + +MOTOR_RANGE = (0.0, 8.0) +OUTPUT_SETPOINTS = (1.0, 2.0, 3.0) +FIXED_X = True + +writables_names = [pos1.getName()] +readable_names = [det1.getName(), "arr[10]","cm1[3][2]"] +start = [ MOTOR_RANGE[0] if FIXED_X else -1, ] +stop = [ MOTOR_RANGE[1] if FIXED_X else -1] +steps = [int(MOTOR_RANGE[1]-MOTOR_RANGE[0])] + +scan = ManualScan(writables_names, readable_names ,start, stop, steps, monitors = [sin]) + + +#This option is to plot the foe each output value one 1D series, instead of all in a matrix plot +#set_exec_pars(line_plots = (det1, det2)) + +scan.start() +pos1.setSpeed(10.0) +for setpoint1 in frange(MOTOR_RANGE[0], MOTOR_RANGE[1], 1.0, True): + pos1.move(setpoint1) + for setpoint2 in OUTPUT_SETPOINTS: + pos2.write(setpoint2) + scan.append ([setpoint1], [pos1.read()], [det1.read(), det2.read(), cm1.read()]) + + +scan.end() diff --git a/script/Regine.py b/script/Regine.py index 0bc3e40..044fe80 100644 --- a/script/Regine.py +++ b/script/Regine.py @@ -86,7 +86,7 @@ if EXPOSURES: POSITIONERS = POSITIONERS + [exposure_index()] SENSORS = SENSORS + [exposure()] RANGE_E=[0, len(EXPOSURES)-1] - + #set_exec_pars(manual_range=RANGE_E, manual_range_y=RANGE_Y) CUSTOM_PLOT_TYPES[sin]=1 @@ -102,7 +102,7 @@ def gen(): xpos = CENTER_X + x_step*STEP_SIZE_X + (random.random()-0.5)*NOISE*2*STEP_SIZE_X range_y= range(STEPS_Y, -STEPS_Y-1, -1) if (ZIGZAG and(x_index%2 ==1)) else range(-STEPS_Y, STEPS_Y+1) for y_step in range_y: - ypos = CENTER_Y + y_step*STEP_SIZE_Y + (random.random()-0.5)*NOISE*2*STEP_SIZE_Y + ypos = CENTER_Y + y_step*STEP_SIZE_Y + (random.random()-0.5)*NOISE*2*STEP_SIZE_Y if EXPOSURES: range_e= range(len(EXPOSURES)-1,-1, -1) if (ZIGZAG and(y_index%2 ==1)) else range(len(EXPOSURES)) for e in range_e: @@ -116,8 +116,9 @@ def gen(): try: r = vscan(POSITIONERS, SENSORS , gen(), False,\ - SETTLING_TIME, relative=False, zigzag = ZIGZAG, \ - before_read=before_readout, after_read = after_read, \ + SETTLING_TIME, relative=False, zigzag = ZIGZAG, initial_move=False, \ + before_read=before_readout, after_read = after_readout, \ + manual_range=RANGE_X, manual_range_y=RANGE_Y, \ compression = COMPRESSION, enabled_plots=ENABLED_PLOTS, \ keep=False, check_positions=False, plot_types=CUSTOM_PLOT_TYPES) set_return(r) diff --git a/script/ShellCommand.py b/script/ShellCommand.py new file mode 100644 index 0000000..d92ae51 --- /dev/null +++ b/script/ShellCommand.py @@ -0,0 +1,73 @@ +#CAS.setServerPort(5064) +import java.util.function.BiFunction as BiFunction + +class ShellCommand(RegisterBase, RegisterArray): + def __init__(self, name): + RegisterBase.__init__(self, name) + self.val = "" + self.debug=False + self.max_size = 10000 + + def getSize(self): + return 1 + + def doRead(self): + if self.debug: + print "READ: ", self.val + return self.val + + def doWrite(self, val): + self.val = "RUNNING" + + + + + + try: + if self.debug: + print "WRITE: ", val + cmd = str(val[0]) + + class eval_callback(BiFunction): + def apply(self_callback, ret, ex): + try: + if ex is not None: + err=ex.message + if "Exception:" in err: + err = err[err.index("Exception:")+10:].strip() + self.val = "ERR:" + err + else: + self.val = "RET:" + str(ret) + self.val = self.val[0:self.max_size] + except: + err=str(sys.exc_info()[1]) + self.val = "EXC: " + err + if self.debug: + print self.val + + #self.val = cmd + get_context().evalLineBackgroundAsync(cmd).handle(eval_callback()) + except: + err=str(sys.exc_info()[1]) + if "Exception:" in err: + err = err[err.index("Exception:")+10:].strip() + self.val = "EXC: " + err + self.val = self.val[0:self.max_size] + if self.debug: + print self.val + + + +add_device(ShellCommand("sc"), True) +cas = CAS("TESTCAS:sc", sc, 'string') + + + +#print caget("TESTCAS:sc","s") +#/Users/gobbo_a/anaconda3/envs/epics/epics/bin/darwin-x86 + + + + + + diff --git a/script/StateCAS.py b/script/StateCAS.py new file mode 100644 index 0000000..e776b9d --- /dev/null +++ b/script/StateCAS.py @@ -0,0 +1,50 @@ +class EpicsServerState(ReadonlyAsyncRegisterBase): + def __init__(self, name, channel, as_string=True): + RegisterBase.__init__(self, name) + self.channel=channel + self.as_string=as_string + self.val = "Unknown" + self.cas = None + self.state_change_listener=None + + def doInitialize(self): + super(EpicsServerState, self).doInitialize() + if self.as_string: + self.cas = CAS(self.channel, self, 'string') + else: + self.cas = CAS(self.channel, self, 'byte') + if self.state_change_listener is None: + class StateChangeListener(ContextListener): + def onContextStateChanged(_self, state, former): + self.set(state) + self.state_change_listener = StateChangeListener() + get_context().addListener(self.state_change_listener) + + def getSize(self): + if self.as_string: + return 1 + else: + return 100 + + def doClose(self): + if self.state_change_listener: + get_context().removeListener(self.state_change_listener) + if self.cas: + self.cas.close() + self.cas = None + super(EpicsServerState, self).doClose() + + def set(self, value): + if self.as_string: + self.onReadout(str(value)) + else: + self.onReadout(string_to_list(str(value))) + + + + + +if "server_state_channel" in globals(): + server_state_channel.close() +server_state_channel = EpicsServerState("server_state_channel", "PSHELL_OP:STATE", False) +server_state_channel.initialize() \ No newline at end of file diff --git a/script/Test.xml b/script/Test.xml index 299bae3..e69de29 100644 --- a/script/Test.xml +++ b/script/Test.xml @@ -1,41 +0,0 @@ - - - - - - - - - - - 0.0 - 10.0 - 1.0 - - - - - - - - - - - - diff --git a/script/Test3.xml b/script/Test3.xml new file mode 100644 index 0000000..dab51b7 --- /dev/null +++ b/script/Test3.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + 0.0 + 10.0 + 1.0 + + + + + + + + + + + + + + + + diff --git a/script/Test4.xml b/script/Test4.xml new file mode 100644 index 0000000..0935959 --- /dev/null +++ b/script/Test4.xml @@ -0,0 +1,24 @@ + + + + + + + + + + 0.0 + 10.0 + 1.0 + + + + + 1 + + + + + + + diff --git a/script/Test4b.xml b/script/Test4b.xml new file mode 100644 index 0000000..a8ca934 --- /dev/null +++ b/script/Test4b.xml @@ -0,0 +1,19 @@ + + + + + + + + + + 0.0 + 0.0 + 1.0 + NINT + + + + + + diff --git a/script/Test4c.xml b/script/Test4c.xml new file mode 100644 index 0000000..b1da0ed --- /dev/null +++ b/script/Test4c.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + var + + + + + + diff --git a/script/Test4e.xml b/script/Test4e.xml new file mode 100644 index 0000000..9c8534c --- /dev/null +++ b/script/Test4e.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + 0.0 + 0.0 + 1.0 + V + + + + + + + 1 + + + + + + + diff --git a/script/Test5.xml b/script/Test5.xml new file mode 100644 index 0000000..5dbb75c --- /dev/null +++ b/script/Test5.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/script/TriggerScan.py b/script/TriggerScan.py index b357a9b..094be76 100755 --- a/script/TriggerScan.py +++ b/script/TriggerScan.py @@ -6,18 +6,25 @@ class Trigger(ReadonlyRegisterBase): trigger = Trigger() trigger.initialize() -sca = "asd" +scan = None def scan(): - global sca - sca = mscan(trigger, [sc1, sin.cache], 20) + global scan + scan = mscan(trigger, [out, sin.cache]) + print scan -scan_task = fork(scan) +scan_task = fork(scan)[0] time.sleep(0.5) -for i in range(10): +for i in range(20): trigger.update() time.sleep(random.random()/5) -#ret = join(scan_task) +time.sleep(1.0) +ep=get_exec_pars() + +ep.currentScan.abort() +#scan_task.cancel(True) + +ret = join(scan_task) diff --git a/script/align/Demo2D.py b/script/align/Demo2D.py new file mode 100644 index 0000000..62d4e37 --- /dev/null +++ b/script/align/Demo2D.py @@ -0,0 +1,41 @@ +################################################################################################### +# Using bsearch(Binary Search) and hsearch(Hill Climbing Search) to find optimum +################################################################################################### + +class Sensor(ReadonlyRegisterBase): + def doRead(self): + return 1000.0 - (math.pow(motor.take()-0.5, 2) + math.pow(pe.take()+2, 2)) +add_device(Sensor("sensor"), True) + +range_x, step_x=1.0, 0.1 +range_y, step_y=10.0, 0.1 + +motor.speed=5.0 +motor.move(0.0) +pe.move(0.0) + +print "--------------- Full 2D Scan -----------------" +r = ascan([motor, pe], sensor, [-range_x,-range_y], [range_x,range_y], [step_x, step_x], relative=True, zigzag=True) +data = Convert.reshape(r[sensor], r.scan.numberOfSteps[0]+1, r.scan.numberOfSteps[1]+1) +data = [(data[i][::-1] if ((i%2)==1) else data[i]) for i in range(len(data))] +d=Data(to_array(data,'d')) +v,h=d.integrateVertically(), d.integrateHorizontally() +mv=fit(v)[1] +mh=fit(h)[1] +index = int(round(mh))*(r.scan.numberOfSteps[1]+1) + int(round(mv)) +max_pe, max_motor= r[pe][index], r[motor][index] +print "2s scan peak at motor=" , max_motor , " pe=", max_pe +motor.move(max_motor) +pe.move(max_pe) + +print "--------------- Binary Search -----------------" +motor.move(0.0);pe.move(0.0) +r = bsearch([motor, pe], sensor, [-range_x,-range_y], [range_x,range_y], [step_x/4, step_y/4], \ + relative=True, maximum=True, strategy = "Normal", restore_position=False ) +print "bsearch peak at position=", r.optimalPosition , " in ", len(r), "evaluations" + +motor.move(0.0);pe.move(0.0) +print "--------------- Hill Climbing Search -----------------" +r = hsearch([motor, pe], sensor, [-range_x,-range_y],[range_x,range_y], [step_x*2, step_y*2], \ + [step_x/4, step_y/4], relative=True, maximum=True, restore_position=False ) +print "hsearch peak at position=", r.optimalPosition, " in ", len(r), "evaluations" \ No newline at end of file diff --git a/script/align/SimpleDemo.py b/script/align/SimpleDemo.py new file mode 100644 index 0000000..19f96f4 --- /dev/null +++ b/script/align/SimpleDemo.py @@ -0,0 +1,33 @@ +################################################################################################### +# Simple alignment example +################################################################################################### + + +from mathutils import * +from plotutils import * + +scan_range = 2.0 +step_size = 0.1 + +r = lscan(motor,sinp,-scan_range,scan_range,[step_size,],relative=True) + +p=get_plots()[0] +p.setLegendVisible(True) +y = r[sinp] +x = r[motor] + +fit_pars = (offset, normalization, mean_val, sigma) = fit_gaussian_offset(y, x) +print fit_pars + +if (mean_val is not None): + gaussian = GaussianOffset(*fit_pars) + plot_function(p, gaussian, "Fit" , x) + +#Fitting error +if mean_val is None or mean_valx[-1]: + mean_val= x[y.index(max(y))] + print "Fitting error - using max value" + + +p.addMarker(mean_val, None, "Mean=" + str(round(mean_val,2)), Color.LIGHT_GRAY) +motor.move(mean_val) \ No newline at end of file diff --git a/script/bsread_camera.py b/script/bsread_camera.py index 21938f3..b6d7725 100644 --- a/script/bsread_camera.py +++ b/script/bsread_camera.py @@ -1,4 +1,4 @@ - +import java.lang.Short as Short import ch.psi.pshell.epics.CAS as CAS import ch.psi.pshell.device.Register.RegisterArray as RegisterArray import random @@ -12,6 +12,7 @@ EPICS_PV_SUFFIX_STATUS = ":INIT" EPICS_PV_SUFFIX_WIDTH = ":WIDTH" EPICS_PV_SUFFIX_HEIGHT = ":HEIGHT" EPICS_PV_SUFFIX_STREAM_ADDRESS = ":BSREADCONFIG" +EPICS_PV_SUFFIX_IMG = ":FPICTURE" class Scalar(RegisterBase): def __init__(self, name): @@ -29,6 +30,7 @@ add_device(Scalar("cam_width"), True) add_device(Scalar("cam_height"), True) add_device(Scalar("cam_add"), True) + cam_width.write(IMG_WIDTH) cam_height.write(IMG_HEIGHT) cam_init.write("INIT") @@ -42,6 +44,13 @@ cas3 = CAS(camera + EPICS_PV_SUFFIX_HEIGHT, cam_height, 'int') cas4 = CAS(camera + EPICS_PV_SUFFIX_STREAM_ADDRESS, cam_add, 'string') +class Img(ReadonlyRegisterBase, ReadonlyRegisterArray): + def doRead(self): + return Convert.toPrimitiveArray(Convert.flatten(cam_server.getArray()),Short) +add_device(Img("cam_img"), True) +cas5 = CAS(camera + EPICS_PV_SUFFIX_IMG, cam_img, 'double') + + print caget(camera + EPICS_PV_SUFFIX_STATUS) print caget(camera + EPICS_PV_SUFFIX_WIDTH) print caget(camera + EPICS_PV_SUFFIX_HEIGHT) diff --git a/script/calc.py b/script/calc.py index 0006d1e..ba18b58 100755 --- a/script/calc.py +++ b/script/calc.py @@ -1,3 +1,4 @@ def calc(a): return a*2 + \ No newline at end of file diff --git a/script/cpy/BugMonitor.py b/script/cpy/BugMonitor.py new file mode 100644 index 0000000..c169da5 --- /dev/null +++ b/script/cpy/BugMonitor.py @@ -0,0 +1,2 @@ +av = create_averager(sin, 5 , monitored=True) +tscan ([sin,av], 100, 0.1) \ No newline at end of file diff --git a/script/cpy/Device.py b/script/cpy/Device.py new file mode 100644 index 0000000..b80b573 --- /dev/null +++ b/script/cpy/Device.py @@ -0,0 +1,170 @@ +class GenericDevice(Nameable): + cls=[ "ch.psi.pshell.device.GenericDevice", \ + "ch.psi.utils.Observable", \ + "ch.psi.pshell.device.Timestamped", \ + "java.lang.AutoCloseable", \ + "ch.psi.utils.Configurable", \ + "ch.psi.pshell.device.Record" ] + + def __init__(self, name=None, cls=GenericDevice.cls): + Nameable.__init__(self, name, cls) + self.simulated=False + self.state=State.Ready + self.monitored=False + self.polling=None + self.cache=None + self.age=None + + + def addListener(self, listener): + pass + + def getListeners(self): + pass + + def removeListener(self, listener): + pass + + def removeAllListeners(self): + pass + + def getTimestamp(self): + return 0 + + def getTimestampNanos(self): + return 0 + + def getConfig(self): + return None + + def getState(self): + return State.Ready + + def waitState(self, state, timeout): + return + + def waitStateNot(self, state, timeout): + pass + + def initialize(self): + pass + + def isInitialized(self): + return True + + def waitInitialized(self, timeout): + pass + + def setSimulated(self): + self.simulated = True + + def isSimulated(self): + return self.simulated + + def isMonitored(self): + return self.monitored + + def setMonitored(self,monitored): + self.monitored=self.monitored + + def setPolling(self, interval): + self.polling=interval + + def getPolling(self): + return self.polling + + def isPollingBackground(self): + return self.polling and self.polling>0 + + def setAccessType(self, mode): + pass + + def getAccessType(self): + return None + + def take(self): + return self.cache + + def getAge(self): + return self.age + + def request(self): + self.update() + + def update(self): + pass + + def setWaitSleep(self, value): + pass + + def getWaitSleep(self): + return 5 + + def isPolled(self): + return self.getPolling() > 0 + + def takeAsNumber(self): + cache = take(); + try: + return float(cache) + except: + return None + + def close(self): + pass + +class Device(GenericDevice): + cls="ch.psi.pshell.device.Device" + + def __init__(self, name=None): + GenericDevice.__init__(self, name, [Device.cls] + GenericDevice.cls) + + def isReady(): + return getState()==State.Ready + + def waitReady(self, timeout): + return + + def waitValue(self, value, timeout): + return + + def waitValueNot(self, value, timeout): + return + + def waitValueChange(self,timeout): + return false + + def waitCacheChange(self,timeout): + return false + + def getComponents(self): + return None + + def getComponent(self, name): + return None + + def getChildren(self): + return None + + def getChild(self, name): + return None + + def getParent(self): + return None + + def setTriggers(self,triggers): + pass + + def getTriggers(self): + return None + + def takeTimestamped(self,): + return None + + def isChild(self, device): + return False + + + +dev= Device("test") +add_device(dev, True) \ No newline at end of file diff --git a/script/cpy/ManualScan.py b/script/cpy/ManualScan.py new file mode 100644 index 0000000..f253e46 --- /dev/null +++ b/script/cpy/ManualScan.py @@ -0,0 +1,43 @@ +################################################################################################### +#Manual scan: Manually setting positioners and reading back sensors, but still using +#the standard data handling and plotting of built-in scans. +################################################################################################### + + +mu.setSpeed(10.0) + +pos1 = mu +pos2 = out +det1 =rs1 +det2 = sin#rw1 +det3 = out#ri1 + + + + +MOTOR_RANGE = (0.0, 8.0) +OUTPUT_SETPOINTS = (1.0, 2.0, 3.0) +FIXED_X = True + +writables_names = [pos1.getName(), pos2.getName()] +readable_names = [det1.getName(), det2.getName()] +start = [ MOTOR_RANGE[0] if FIXED_X else -1, OUTPUT_SETPOINTS[0]] +stop = [ MOTOR_RANGE[1] if FIXED_X else -1, OUTPUT_SETPOINTS[-1]] +steps = [int(MOTOR_RANGE[1]-MOTOR_RANGE[0]), len(OUTPUT_SETPOINTS)-1] + +scan = ManualScan(writables_names, readable_names ,start, stop, steps, monitors = [sin]) + + +#This option is to plot the foe each output value one 1D series, instead of all in a matrix plot +set_exec_pars(line_plots = (det1, det2)) + +scan.start() +pos1.setSpeed(10.0) +for setpoint1 in frange(MOTOR_RANGE[0], MOTOR_RANGE[1], 1.0, True): + pos1.move(setpoint1) + for setpoint2 in OUTPUT_SETPOINTS: + pos2.write(setpoint2) + scan.append ([setpoint1, setpoint2], [pos1.read(), pos2.read()], [det1.read(), det2.read()]) + + +scan.end() diff --git a/script/cpy/Signal.py b/script/cpy/Signal.py new file mode 100644 index 0000000..0a3157b --- /dev/null +++ b/script/cpy/Signal.py @@ -0,0 +1,14 @@ +import signal + +def is_main_thread(): + try: + # Backup the current signal handler + back_up = signal.signal(signal.SIGINT, signal.SIG_DFL) + except ValueError: + # Only Main Thread can handle signals + return False + # Restore signal handler + signal.signal(signal.SIGINT, back_up) + return True +print (is_main_thread()) + \ No newline at end of file diff --git a/script/cpy/TestBlueskyPause.py b/script/cpy/TestBlueskyPause.py new file mode 100644 index 0000000..7e99f30 --- /dev/null +++ b/script/cpy/TestBlueskyPause.py @@ -0,0 +1,48 @@ +from functools import partial +import threading +import traceback +import socket + +CTRL_CMD_PORT = 9587 +msg=None +if ("ctrl_cmd_socket" in globals()) and (ctrl_cmd_socket is not None): + ctrl_cmd_socket.close() + ctrl_cmd_task_thread.join(5.0) + if ctrl_cmd_task_thread.is_alive(): + raise Exception("Cannot stop ctrl_cmd_task_thread") + +def on_ctrl_cmd(cmd): + global RE + print ("Control command: ", cmd) + if cmd=="abort": + if "RE" in globals(): + if RE.state not in ['idle','paused', 'pausing']: + print ("Run Engine pause request") + RE.request_pause() + +def ctlm_cmd_task(port,parent_thread, rc): + try: + global ctrl_cmd_socket + print ("Starting control command task") + quit=False + with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as ctrl_cmd_socket: + ctrl_cmd_socket.bind(("127.0.0.1", port)) + ctrl_cmd_socket.settimeout(2.0) + while(quit==False) and (run_count==rc) and parent_thread.is_alive() and not ctrl_cmd_socket._closed: + try: + msg,add = ctrl_cmd_socket.recvfrom(100) + except socket.timeout: + continue + cmd =msg.decode('UTF-8') + on_ctrl_cmd(cmd) + if cmd=="exit": + quit=True + ctrl_cmd_socket.sendto("ack".encode('UTF-8'), add) + finally: + print("Quitting control command task") + +ctrl_cmd_task_thread = threading.Thread(target=partial(ctlm_cmd_task, CTRL_CMD_PORT, threading.currentThread(), run_count)) +ctrl_cmd_task_thread.setDaemon(True) +ctrl_cmd_task_thread.start() + +#RE(rel_scan(dets, motor, -1, 1, 10)) \ No newline at end of file diff --git a/script/cpy/TestJProxy.py b/script/cpy/TestJProxy.py new file mode 100644 index 0000000..5f988f2 --- /dev/null +++ b/script/cpy/TestJProxy.py @@ -0,0 +1,75 @@ +from jep import jproxy +import random + + +class MyWritable(Writable): + def write(self, value): + print ("Write: ",value) + +class MyReadable(Readable): + def read(self): + return random.random() + +class MyReadableArray(ReadableArray): + def read(self): + ret = [] + for i in range (self.getSize()): + ret.append(random.random()) + return ret + + def getSize(self): + return 20 + +class MyReadableCalibratedArray(ReadableCalibratedArray): + def read(self): + return rw1.read() + + def getSize(self): + return rw1.getSize() + + def getCalibration(self): + return ArrayCalibration(5,1000) + +class MyReadableMatrix(ReadableMatrix): + def read(self): + ret = [] + for i in range (self.getHeight()): + ret.append([random.random()] * self.getWidth()) + return to_array(ret, 'd') + + def getWidth(self): + return 80 + + def getHeight(self): + return 40 + + +class MyReadableCalibratedMatrix(ReadableCalibratedMatrix): + def read(self): + return ri1.read() + + def getWidth(self): + return ri1.getWidth() + + def getHeight(self): + return ri1.getHeight() + + def getCalibration(self): + return MatrixCalibration(2,4,100,200) + + +#plot([1,2,3]) + +ws1 = MyWritable("ws1") +rs1 = MyReadable("rs1") +rw1 = MyReadableArray("rw1") +ri1 = MyReadableMatrix("ri1") +ac1 = MyReadableCalibratedArray("ac1") +mc1 = MyReadableCalibratedMatrix("mc1") + + + +t=lscan(ws1, (rs1, rw1, ri1, ac1, mc1), 0, 5, 5) + + + diff --git a/script/cpy/TestJProxy2.py b/script/cpy/TestJProxy2.py new file mode 100644 index 0000000..e071fc2 --- /dev/null +++ b/script/cpy/TestJProxy2.py @@ -0,0 +1,68 @@ +from jep import jproxy +import random + + +""" +class proxy(): + def __init__(self,cls): + self.cls=cls + def __call__(self, name): + return jproxy(self, self.cls) + +@proxy(['ch.psi.pshell.device.Readable']) +""" +class Readable(): + def __init__(self, name=None): + self.name=name + self.proxy=jproxy(self, ['ch.psi.pshell.device.Readable']) + + def __call__(self): + return self.read() + + def __getattribute__(self, name): + if name in ('proxy',): + return object.__getattribute__(self, name) + print(name) + return self.proxy.__getattribute__(name) + + def getName(self): + if self.name : + return self.name + + return str(self.__class__.__name__) + def read(self): + raise Exception ("Not implemented") + + + +class ReadableScalar(Readable): + def read(self): + ret =random.random() + print (ret) + return ret + +class ReadableWaveform(Readable): + + def __call__(self): + return self.read() + def getSize(self): + return 20 + def read(self): + ret = [] + for i in range (self.getSize()): + ret.append(random.random()) + return ret + def getName(self): + return "rw1" + + + +#rs1 = jproxy(ReadableScalar(), ['ch.psi.pshell.device.Readable']) +rs1 = ReadableScalar() +#rw1 = jproxy(ReadableWaveform(), ['ch.psi.pshell.device.Readable$ReadableArray']) +#ri1 = jproxy(ReadableImage(), ['ch.psi.pshell.device.Readable$ReadableMatrix']) +#rw1=ReadableWaveform() +rs1.read() +#rw1.read() +#rs1 = jproxy(rs1 , ['ch.psi.pshell.device.Readable'])# +#tscan([rs1],10,0.1, save=False) \ No newline at end of file diff --git a/script/cpy/TestJepError.py b/script/cpy/TestJepError.py new file mode 100644 index 0000000..f58f531 --- /dev/null +++ b/script/cpy/TestJepError.py @@ -0,0 +1,97 @@ +################################################################################################### +#Data Manipulation: Using the data access API to generate and retrieve data +################################################################################################### + + +#Creating a 1D dataset from an array +path="group/data1" +data1d = [1.0, 2.0, 3.0, 4.0, 5.0] +save_dataset(path, data1d) +#Reading ii back +read =load_data(path) +print (list(read)) +assert data1d==list(read) +plot(read) + +#Creating a 2D dataset from an array with some attributes +data2d = [ [1.0, 2.0, 3.0, 4.0, 5.0], [2.0, 3.0, 4.0, 5.0, 6.0, ], [3.0, 4.0, 5.0, 6.0, 7.0]] +path="group/data2" +save_dataset(path, data2d) +set_attribute(path, "AttrString", "Value") +set_attribute(path, "AttrInteger", 1) +set_attribute(path, "AttrDouble", 2.0) +set_attribute(path, "AttrBoolean", True) +#Reading it back +read =load_data(path) +print (list(read)) +plot(read) + +#Creating a 3D dataset from an array +data3d = [ [ [1,2,3,4,5], [2,3,4,5,6], [3,4,5,6,7]], [ [3,2,3,4,5], [4,3,4,5,6], [5,4,5,6,7]]] +path="group/data3" +save_dataset(path, data3d) +#Reading it back +read =load_data(path,0) +print (list(read)) +read =load_data(path,1) +print (list(read)) + +#Creating a INT dataset adding elements one by one +path = "group/data4" +create_dataset(path, 'i') +for i in range(10): + append_dataset(path,i) + + +#Creating a 2D data FLOAT dataset adding lines one by one +path = "group/data5" +create_dataset(path, 'd', False, (0,0)) +for row in data2d: + append_dataset(path, row) + + +#Creating a Table (compund type) +path = "group/data6" +names = ["a", "b", "c", "d"] +types = ["d", "d", "d", "[d"] +lenghts = [0,0,0,5] +table = [ [1,2,3,[0,1,2,3,4]], + [2,3,4,[3,4,5,6,7]], + [3,4,5,[6,7,8,9,4]] ] +create_table(path, names, types, lenghts) +for row in table: + append_table(path, row) +flush_data() +#Read it back +read =load_data(path) +print (read) + + +#Writing scalars (datasets with rank 0) +save_dataset("group/val1", 1) +save_dataset("group/val2", 3.14) +save_dataset("group/val3", "test") +print (load_data("group/val1")) +print (load_data("group/val2")) +print (load_data("group/val3")) + + + + + + +time.sleep(1.0) + + +code="""def is_java_instance(obj, cls): + print(1) +################################################################################################### +#Access to context singleton +################################################################################################### +def get_context(): + return core.Context.getInstance() +""" + +comp=compile(code, '', 'exec') + +set_return(2) \ No newline at end of file diff --git a/script/cpy/bluesky.py b/script/cpy/bluesky.py new file mode 100644 index 0000000..71443a9 --- /dev/null +++ b/script/cpy/bluesky.py @@ -0,0 +1,178 @@ +import signal +signal.signal = lambda *args: None +################################################################################################### +#import matplotlib +#matplotlib.use('Qt5Agg') +import traceback + + +from bluesky import RunEngine +from bluesky.callbacks.best_effort import BestEffortCallback +from bluesky.utils import install_kicker + +from bluesky.utils import ProgressBarManager +from ophyd.sim import det1, det2, det3, det4, det, motor, motor1, motor2, motor3,img, sig, direct_img, pseudo1x3 +from ophyd import Signal +from ophyd.signal import EpicsSignal +from bluesky.plans import count, scan, rel_scan, list_scan, grid_scan, list_grid_scan +from bluesky.simulators import summarize_plan +import bluesky.plan_stubs as bps +from bluesky.plan_stubs import mv +import bluesky.preprocessors as bpp +from bluesky.preprocessors import SupplementalData +import databroker +#import suitcase +#import suitcase.csv +#from databroker import Broker + + +RE = RunEngine({}) +bec = BestEffortCallback() +bec.disable_plots() +RE.subscribe(bec) +#db = Broker.named('temp') +#RE.subscribe(db.insert) +#RE.waiting_hook = ProgressBarManager() +dets = [det1, det2] + + +RE(count(dets, num=5)) +RE(scan(dets, motor, -1, 1, 10)) +dir (motor) + +vel=motor.velocity.get() +motor.velocity.set(vel+1) +RE(scan(dets, motor, -1, 1, 10)) +det.read() +det.get() +dets=[det] + +RE(rel_scan(dets, motor, -1, 1, 10)) + +points = [1, 1, 2, 3, 5, 8, 13] +RE(list_scan(dets, motor, points)) + +dets=[det4] +RE(scan(dets,motor1, -1.5, 1.5, motor2, -0.1, 0.1, 11)) +RE(list_scan(dets, motor1, [1, 1, 3, 5, 8], motor2, [25, 16, 9, 4, 1])) +RE(grid_scan(dets, motor1, -1.5, 1.5, 3, motor2, -0.1, 0.1, 5, motor3, 10, -10, 5)) + +RE(grid_scan(dets, motor1, -1.5, 1.5, 3, motor2, -0.1, 0.1, 5)) +RE(grid_scan(dets, motor1, -1.5, 1.5, 3, motor2, -0.1, 0.1, 5, motor3, 10, -10, 5)) + +RE(list_grid_scan(dets, motor1, [1, 1, 2, 3, 5], motor2, [25, 16, 9])) + + +db[-1].table() + + + +def coarse_and_fine(detectors, motor, start, stop): + yield from scan(detectors, motor, start, stop, 10) + yield from scan(detectors, motor, start, stop, 100) + RE(coarse_and_fine(dets, motor, -1, 1)) + +# Move motor1 to 1 and motor2 to 10, simultaneously. Wait for both to arrive. +RE(mv(motor1, 1, motor2, 10)) +print (motor1.get().readback, motor2.get().readback) + +# Move motor1 to 1 and motor2 to 10, simultaneously. Wait for both to arrive. +print (motor1.get(), motor2.get()) +def move_then_count(): + yield from mv(motor1, 1, motor2, 10) + yield from count(dets) +RE(move_then_count()) + + +sd = SupplementalData() +RE.preprocessors.append(sd) +sd.baseline = [det1, det2, det3, motor1, motor2] +RE(scan([det], motor, -1, 1, 5)) +print(db[-1].table("baseline")) +print(db[-1].table("primary")) + + +""" +docs = db[-1].documents(fill=True) +try: + suitcase.csv.export(docs, "~/data/bluesky") +except: + print (sys.exc_info()[1]) +""" + +motor.delay = 1.1 # simulate slow motor movement +sd.monitors=[motor] +RE(scan(dets, motor, -1, 1, 10)) +print(db[-1].table("baseline")) +print(db[-1].table("monitor")) + + +#RE.resume() +#RE.abort() +#RE.stop() + + +RE(scan([det], motor, 1, 10, 10), user="alex") +for x in db(user="alex"): + print (x.table()) + + +summarize_plan(count([det], 3)) +summarize_plan(scan(dets, motor, -1, 1, 10)) +summarize_plan(grid_scan(dets, motor1, -1.5, 1.5, 3, motor2, -0.1, 0.1, 5)) + + +def one_run_one_event(detectors): + + md = { + # Human-friendly names of detector Devices (useful for searching) + 'detectors': [det.name for det in detectors], + + # The Python 'repr's each argument to the plan + 'plan_args': {'detectors': list(map(repr, detectors))}, + + # The name of this plan + 'plan_name': 'one_run_one_event', + } + @bpp.stage_decorator(detectors) + @bpp.run_decorator(md=md) + def inner(): + yield from bps.trigger_and_read(detectors) + + return (yield from inner()) + +dets = [det1,det2] +RE(one_run_one_event(dets)) + + +def conditional_break(threshold): + """Set, trigger, read until the detector reads intensity < threshold""" + + @bpp.stage_decorator([det, motor]) + @bpp.run_decorator() + def inner(): + i = 0 + while True: + yield from bps.mv(motor, i) + readings = yield from bps.trigger_and_read([det]) + if readings['det']['value'] < threshold: + break + i += 1 + return (yield from inner()) + +RE(conditional_break(0.2)) + +#catalog = databroker.catalog() + +dets=[pseudo1x3] +RE(scan(dets, motor, -1, 1, 10)) + +es = EpicsSignal("TESTIOC:TESTSINUS:SinCalc") +print (es.read()) + + +try: + plot(direct_img.read()['img']['value'].data) + +except: + traceback.print_exc() \ No newline at end of file diff --git a/script/cpy/bluesky2.py b/script/cpy/bluesky2.py new file mode 100644 index 0000000..b8462a0 --- /dev/null +++ b/script/cpy/bluesky2.py @@ -0,0 +1,30 @@ +""" +RE = RunEngine({}, during_task=EventProcessingTask()) +bec = BestEffortCallback() +bec.disable_plots() +RE.subscribe(bec) +RE.subscribe(handler) + +motor.delay = 1.1 # simulate slow motor movement + + +ch1 = EpicsSignal("TESTIOC:TESTSINUS:SinCalc") +#TODO: DEmonstrate use of waveform and areadetector (Manual scan setup with the indexes in name) +#ch2 = EpicsSignal("TESTIOC:TESTWF2:MyWF", name="arr[10]") +#ch3 = EpicsSignal("TESTIOC:TESTWF2:MyWF", name="img[3][2]")det3=ReaderWrapper(sin) +""" +dets = [det1, det2] + +RE(count(dets, num=5, delay=0.5)) +RE(scan(dets, motor, 0, 1, 5)) +RE(rel_scan(dets, motor, -1, 1, 10)) +RE(list_scan(dets, motor, [1, 1, 2, 3, 5, 8, 13])) +RE(grid_scan(dets, motor1, -1.5, 1.5, 3, motor2, -0.1, 0.1, 5)) +RE(list_grid_scan(dets, motor1, [1, 1, 2, 3, 5], motor2, [25, 16, 9])) + + +det4=ReaderWrapper(arr) +det5=ReaderWrapper(get_device("det").getDataMatrix() ) +m1=MoverWrapper(get_device("motor")) +dets = [det1, det2, ch1, det3, det4, det5] +RE(scan(dets, m1, 0, 1, 5)) diff --git a/script/cpy/local.py b/script/cpy/local.py new file mode 100644 index 0000000..847b204 --- /dev/null +++ b/script/cpy/local.py @@ -0,0 +1,487 @@ +################################################################################################### +# Deployment specific global definitions - executed after startup.py +################################################################################################### + +################################################################################################### +#Devices for PShell standard scans +################################################################################################### + +import random + +#################################################################################################### +# Simulated devices +#################################################################################################### + +add_device(DummyMotor("m1"), True) +add_device(DummyMotor("m2"), True) +add_device(DummyRegister("reg1",3), True) +add_device(DummyPositioner("p1"),True) +add_device(MotorGroupBase("mg1", m1, m2), True) +add_device(MotorGroupDiscretePositioner("dp1", mg1), True) + + +#Initial Configuration +if p1.getConfig().unit is None: + p1.getConfig().minValue = 0.0 #Not persisted + p1.getConfig().maxValue = 1000.0 + p1.getConfig().unit = "mm" + p1.getConfig().save() + p1.initialize() + +if dp1.getConfig().positions is None: + dp1.getConfig().positions = ["Park","Ready","Out","Clear"] + dp1.getConfig().motor1 = ["0.0","4.0","8.0" ,"0.0"] + dp1.getConfig().motor2 = ["0.0","5.0","3.0" ,"NaN"] + dp1.getConfig().save() + dp1.initialize() + + + +#Update +m1.setMonitored(True) +m2.setMonitored(True) + +#################################################################################################### +# Readable / Writable objects can be created and used in scans +#################################################################################################### + +class MyWritable(Writable): + def write(self, value): + #print ("Write: ",value) + pass + +class MyReadable(Readable): + def read(self): + return random.random() + +class MyReadableArray(ReadableArray): + def read(self): + ret = [] + for i in range (self.getSize()): + ret.append(random.random()) + return to_array(ret,'d') + + def getSize(self): + return 20 + +class MyReadableArrayNumpy(ReadableArray): + def read(self): + ret = numpy.ones(self.getSize(),'d') + return ret + + def getSize(self): + return 20 + +class MyReadableMatrix(ReadableMatrix): + def read(self): + ret = [] + for i in range (self.getHeight()): + ret.append([random.random()] * self.getWidth()) + return to_array(ret, 'd') + + def getWidth(self): + return 80 + + def getHeight(self): + return 40 + +class MyReadableMatrixNumpy(ReadableMatrix): + def read(self): + ret = numpy.ones((self.getHeight(), self.getWidth()),'d') + for i in range(self.getHeight()): + ret[i]=i + return to_array(ret, 'd') + + def getWidth(self): + return 80 + + def getHeight(self): + return 40 + + +ao1 = MyWritable("ao1") +ao2 = MyWritable("ao2") +ai1 = MyReadable("ai1") +ai2 = MyReadable("ai2") +wf1 = MyReadableArray("wf1") +wf2 = MyReadableArrayNumpy("wf2") +im1 = MyReadableMatrix("im1") +im2 = MyReadableMatrixNumpy("im2") + + +#################################################################################################### +# Imaging +#################################################################################################### + +configured = os.path.exists(GenericDevice.getConfigFileName("src1")) + +add_device(RegisterMatrixSource("src1", im1.proxy), True) +add_device(RegisterMatrixSource("src2", im2.proxy), True) + +#src1.setPolling(100) +#src2.setPolling(100) + +#Some configuration for so the imaging will work out of the box +if not configured: + src1.getConfig().colormapAutomatic = True + src1.getConfig().colormap = Colormap.Temperature + src1.getConfig().save() + src2.getConfig().colormapAutomatic = True + src2.getConfig().save() + + + + +################################################################################################### +#Embedding Bluesky +################################################################################################### + + +import signal +signal.signal = lambda *args: None +from bluesky import RunEngine +from bluesky.callbacks import CallbackBase +from bluesky.callbacks.best_effort import BestEffortCallback +from bluesky.utils import install_kicker, DuringTask +#from bluesky.utils import ProgressBarManager +from ophyd.sim import det1, det2, det3, det4, det, motor, motor1, motor2, motor3,img, sig, direct_img, pseudo1x3 +from ophyd import Signal +from ophyd.signal import EpicsSignal +from bluesky.plans import count, scan, rel_scan, list_scan, grid_scan, list_grid_scan +from bluesky.simulators import summarize_plan +import bluesky.plan_stubs as bps +from bluesky.plan_stubs import mv +import bluesky.preprocessors as bpp +from bluesky.preprocessors import SupplementalData +#import databroker +from databroker import Broker +#import suitcase +#import suitcase.csv + +CAN_PAUSE=True + + + +#def on_ctrl_cmd(cmd): +def on_abort(parent_thread): + global RE + # print ("Control command: ", cmd) + # if cmd=="abort": + if "RE" in globals(): + if RE.state not in ['idle','paused', 'pausing']: + if CAN_PAUSE: + print ("Abort command: Run Engine aborted") + sys.stderr=None + RE.abort("User abort") + return + else: + print ("Abort command: Run Engine pause request") + RE.request_pause() + return + tid=parent_thread.ident + exception = KeyboardInterrupt + ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exception)) + +def on_close(parent_thread): + on_abort(parent_thread) + + + + +from collections import deque + +is_scan_paused = False +class MyHandler(CallbackBase): + + def __init__(self): + self.queue= deque(maxlen=1000) + + def clear(self): + self.queue.append(("clear",None)) + + def start(self, doc): + self.queue.append(("start",doc)) + + def stop(self, doc): + self.queue.append(("stop",doc)) + + def descriptor(self, doc): + self.queue.append(("descriptor",doc)) + + def resource(self, doc): + self.queue.append(("resource",doc)) + + def event(self, doc): + self.queue.append(("event",doc)) + while is_scan_paused: + time.sleep(0.1) + + def datum(self, doc): + self.queue.append(("datum",doc)) + + def event_page(self, doc): + self.queue.append(("event_page",doc)) + + def datum_page(self, doc): + self.queue.append(("datum_page",doc)) + +handler= MyHandler() +re_scan=re_res=None + + +class EventProcessingTask(DuringTask): + def __init__(self): + pass + + def block(self, blocking_event): + global start_doc,descriptor_coc, stop_doc, event_doc, re_scan, re_res, __return__,__exception__ + re_scan=None + while True: + done = blocking_event.wait(.1) + if done: + return + try: + check_pause() + while len(handler.queue): + (msg, doc) = handler.queue.popleft() + #print("-> " ,msg) + if msg=="start": + global start_doc + start_doc=doc + writables=doc.get('motors',[]) + + readables=doc.get('detectors',[]) + start,stop, steps=-1,-1,doc.get('num_intervals',-1) + try: + if doc.get("plan_name","")=="grid_scan": + steps=[x-1 for x in doc['shape']] + start=[x[0] for x in doc['extents']] + stop=[x[1] for x in doc['extents']] + elif doc.get("plan_name","")=="list_grid_scan": + steps=[-1 for x in doc['shape']] + start=[x[0] for x in doc['extents']] + stop=[x[1] for x in doc['extents']] + elif doc.get("plan_name","")=="scan": + _,start,stop = doc['plan_args']['args'] + elif doc.get("plan_name","")=="list_scan": + _,positions=doc['plan_args']['args'] + start = min(positions) + stop = max(positions) + except: + pass + + diags=None + monitors=None + meta={} + for k in start_doc.keys(): + o=start_doc[k] + if o is not None: + if k=="extents": + o = str(o) + elif type(o) in (list, tuple): + o=to_array(o,'s') + elif type(o) == dict: + o=str(o) + meta[k]=o + re_res = None + re_scan = ManualScan(writables, readables ,start, stop, steps, diags=diags, monitors=monitors, meta=meta) + re_scan.scan.setCanPause(CAN_PAUSE) + re_scan.start() + + elif msg=="stop": + global stop_doc + stop_doc=doc + if re_scan is not None: + if not re_scan.scan.isCompleted(): + re_scan.end() + re_res = re_scan.scan.getResult() + sys.stderr=jep_stderr + elif msg=="descriptor": + global descriptor_doc + descriptor_doc=doc + elif msg=="event": + global event_doc + event_doc=doc + if (re_scan is not None) and not (re_scan.scan.isCompleted()): + setpoints=[] + readbacks=[] + detectors=[] + data=doc['data'] + for dev in start_doc.get("motors",[]): + readbacks.append(data[dev]) + try: + setpoints.append(data[dev+"_setpoint"]) + except: + setpoints.append(data[dev]) + for dev in start_doc.get("detectors",[]): + v=data[dev] + if str(type(v))=="": + v=numpy.array(v) + detectors.append(v) + re_scan.append (setpoints, readbacks, detectors) + elif msg=="resource": + pass + elif msg=="datum": + pass + elif msg=="event_page": + pass + elif msg=="datum_page": + pass + elif msg=="clear": + pass + elif msg=="check_pause": + pass + elif msg=="read": + try: + (dev)=doc + __return__ = dev.read() + except Exception as e: + __exception__ = e + elif msg=="write": + try: + (dev, value)=doc + dev.write(value) + __return__ = True + except Exception as e: + __exception__ = e + except Exception as e: + #print (e) + pass + + +def check_pause(): + try: + global RE, re_scan, is_scan_paused + if CAN_PAUSE: + if ("RE" in globals()) and (re_scan is not None) and not (re_scan.scan.isCompleted()): + is_scan_paused =re_scan.scan.isPaused() and not re_scan.scan.isAborted() + else: + is_scan_paused = False + #if re_scan.scan.isAborted(): + # if RE.state not in ['idle','paused', 'pausing']: + # print ("Scan abort") + # RE.abort("Scan abort") + """ + if re_scan.scan.isPaused(): + if RE.state not in ['idle','paused', 'pausing']: + print ("Scan paused: Run Engine pause request") + RE.request_pause() + is_scan_paused = True + else: + if RE.state in ['paused', 'pausing']: + print ("Scan resumed: Run Engine resuming") + #RE.resume() + """ + except: + pass + + +class ReaderWrapper(): + def __init__(self, dev): + self.dev=dev + self.name=dev.getName() + self.parent = None + try: + self.source = self.dev.getChannelName() + except: + self.source = "Unknown" + try: + try: + self.shape = [self.dev.getHeight(), self.dev.getWidth()] + self.shape_str = "["+str(self.shape[0])+"]"+"["+str(self.shape[1])+"]" + except: + self.shape = [self.dev.getSize()] + self.shape_str = "["+str(self.shape[0])+"]" + except: + self.shape = [] + self.shape_str="" + try: + self.precision = self.dev.getPrecision() + except: + self.precision = None + self.description = {self.name: { \ + 'dtype':'number', \ + 'shape': self.shape, \ + 'source': self.source, \ + 'precision': self.precision \ + }} + + self.cfg_description = {"shape_str":{"dtype":"string", 'shape':(len(self.shape_str),), "source":""}, } + self.configuration = {"shape_str":{"value":self.shape_str, "timestamp":time.time()}} + self.name = self.name+self.shape_str + + def read(self): + if is_main_thread(): + return {self.name:{"value":self.dev.read(), "timestamp":time.time()}} + global __return__,__exception__ + __return__ = __exception__ = None + handler.queue.append(("read", (self.dev))) + while (__return__ is None) and (__exception__ is None): + time.sleep(0.01) + if __exception__ is not None: + raise __exception__ + return {self.name:{"value":__return__, "timestamp":time.time()}} + + def describe(self): + return self.description + + def describe_configuration(self): + return self.cfg_description + + def read_configuration(self): + return self.configuration + +class NullStatus: + "a simple Status object that is always immediately done" + def __init__(self): + self._cb = None + self.done = True + self.success = True + + @property + def finished_cb(self): + return self._cb + + @finished_cb.setter + def finished_cb(self, cb): + cb() + self._cb = cb + +class MoverWrapper(ReaderWrapper): + def set(self, value): + if is_main_thread(): + self.dev.write(value) + else: + global __return__,__exception__ + __return__ = __exception__ = None + handler.queue.append(("write", (self.dev, value))) + while (__return__ is None) and (__exception__ is None): + time.sleep(0.01) + if __exception__ is not None: + raise __exception__ + self.dev.waitReady(-1) + return NullStatus() + + @property + def position(self): + return self.dev.getPosition() + + def stop(self, *, success=False): + self.dev.stop() + + + +RE = RunEngine({}, during_task=EventProcessingTask()) +if CAN_PAUSE: + RE.pause_msg="User abort" +bec = BestEffortCallback() +bec.disable_plots() +RE.subscribe(bec) +RE.subscribe(handler) + +motor.delay = 1.1 # simulate slow motor movement +ch1 = EpicsSignal("TESTIOC:TESTSINUS:SinCalc") +#TODO: DEmonstrate use of waveform and areadetector (Manual scan setup with the indexes in name) +#ch2 = EpicsSignal("TESTIOC:TESTWF2:MyWF", name="arr[10]") +#ch3 = EpicsSignal("TESTIOC:TESTWF2:MyWF", name="img[3][2]")det3=ReaderWrapper(sin) +dets = [det1, det2] \ No newline at end of file diff --git a/script/cpy/startup_jep.py b/script/cpy/startup_jep.py new file mode 100644 index 0000000..2855bfc --- /dev/null +++ b/script/cpy/startup_jep.py @@ -0,0 +1,2796 @@ +################################################################################################### +# Global definitions and built-in functions +################################################################################################### + +import sys +sys.argv=[''] +import time +import math +from inspect import signature +import os.path +from operator import add, mul, sub, truediv +from time import sleep +from array import array +import types + +#TODO +#from jep import PyJArray +def is_array(obj): + return str(type(obj)) == "" + +from java.lang import Class +from java.lang import Object +from java.beans import PropertyChangeListener +from java.util import concurrent +from java.util import List +from java.util import ArrayList +from java.lang import reflect +from java.lang import Thread + +from java.awt.image import BufferedImage +from java.awt import Color +from java.awt import Point +from java.awt import Dimension +from java.awt import Rectangle +from java.awt import Font + + +from java.lang import Boolean, Integer, Float, Double, Short, Byte, Long, String + +from ch.psi.pshell import core +from ch.psi.pshell.scripting import ScriptUtils +from ch.psi.utils import Convert +from ch.psi.utils import Arr + +__THREAD_EXEC_RESULT__=None + +################################################################################################### +#Type conversion and checking +################################################################################################### + +def to_array(obj, type = None, primitive = True): + """Convert Python list to Java array. + + Args: + obj(list): Original data. + type(str): array type 'b' = byte, 'h' = short, 'i' = int, 'l' = long, 'f' = float, 'd' = double, + 'c' = char, 'z' = boolean, 's' = String, 'o' = Object + Returns: + Java array. + """ + if obj is None: + return None + if type is None: + type = 'o' + enforceArrayType=False + else: + enforceArrayType=True + if type[0] == '[': + type = type[1:] + element_type = ScriptUtils.getPrimitiveType(type) if primitive else ScriptUtils.getType(type) + + def convert_1d_array(obj): + if type == 'c': + ret = reflect.Array.newInstance(element_type,len(obj)) + for i in range(len(obj)): ret[i] = chr(obj[i]) + return ret + if type == 'z': + ret = reflect.Array.newInstance(element_type,len(obj)) + for i in range(len(obj)): + ret[i]= True if obj[i] else False + return ret + if type == 'o': + ret = reflect.Array.newInstance(element_type,len(obj)) + for i in range(len(obj)): + ret[i]= obj[i] + return ret + if type == "s": + return Convert.toStringArray(obj) + if primitive: + ret = Convert.toPrimitiveArray(obj, element_type) + else: + ret = reflect.Array.newInstance(element_type,len(obj)) + for i in range(len(obj)): ret[i] = Convert.toType(obj[i],element_type) + return ret + + if is_array(obj): + if enforceArrayType: + if Arr.getComponentType(obj) != element_type: + rank = Arr.getRank(obj) + if (rank== 1): + obj=convert_1d_array(obj) + elif (rank>1): + pars, aux = [element_type], obj + for i in range(rank): + pars.append(len(aux)) + aux = aux[0] + ret = reflect.Array.newInstance(*pars) + for i in range(len(obj)): + ret[i]=to_array(obj[i], type) + obj = ret + elif is_list(obj): + if type=='o': + ret = reflect.Array.newInstance(element_type, len(obj)) + for i in range (len(obj)): + if is_list(obj[i]) or is_array(obj[i]): + ret[i] = to_array(obj[i],type) + else: + ret[i] = obj[i] + obj=ret + elif len(obj)>0 and (is_list(obj[0]) or is_array(obj[0])): + pars, aux = [element_type], obj + while len(aux)>0 and (is_list(aux[0]) or is_array(aux[0])): + pars.append(len(aux)) + aux = aux[0] + pars.append(0) + ret = reflect.Array.newInstance(*pars) + for i in range(len(obj)): + ret[i]=to_array(obj[i], type) + obj=ret + else: + obj= convert_1d_array(obj) + return obj + +def to_list(obj): + """Convert an object into a Python List. + + Args: + obj(tuple or array or ArrayList): Original data. + + Returns: + List. + """ + if obj is None: + return None + if isinstance(obj,tuple) or is_java_instance(obj,ArrayList) : + return list(obj) + #if is_array(obj): + # return obj.tolist() + if not isinstance(obj,list): + return [obj,] + return obj + +def is_list(obj): + return isinstance(obj,tuple) or isinstance(obj,list) or is_java_instance (obj, ArrayList) + +def is_string(obj): + return (type(obj) is str) + + +def is_interpreter_thread(): + return Thread.currentThread().getName() == "Interpreter Thread" + + +def is_java_instance(obj, cls): + try: + return obj.getClass() == Class.forName(cls.java_name) + except: + return False +################################################################################################### +#Access to context singleton +################################################################################################### +def get_context(): + return core.Context.getInstance() + +################################################################################################### +#Builtin classes +################################################################################################### + +from ch.psi.utils import Threading as Threading +from ch.psi.utils import State as State +from ch.psi.utils import Convert as Convert +from ch.psi.utils import Str as Str +from ch.psi.utils import Sys as Sys +from ch.psi.utils import Arr as Arr +from ch.psi.utils import IO as IO +from ch.psi.utils import Chrono as Chrono +from ch.psi.utils import Folder as Folder +from ch.psi.utils import Histogram as Histogram +from ch.psi.utils import History as History +from ch.psi.utils import Condition as Condition +from ch.psi.utils import ArrayProperties as ArrayProperties +from ch.psi.utils import Audio as Audio +from ch.psi.utils import BitMask as BitMask +from ch.psi.utils import Config as Config +from ch.psi.utils import Inventory as Inventory +from ch.psi.utils import DataAPI as DataAPI +from ch.psi.utils import DispatcherAPI as DispatcherAPI +from ch.psi.utils import EpicsBootInfoAPI as EpicsBootInfoAPI +from ch.psi.utils import Mail as Mail +from ch.psi.utils import Posix as Posix +from ch.psi.utils import ProcessFactory as ProcessFactory +from ch.psi.utils import Range as Range +from ch.psi.utils import Reflection as Reflection +from ch.psi.utils import Serializer as Serializer +from ch.psi.utils import Windows as Windows +from ch.psi.utils import NumberComparator as NumberComparator + + +from ch.psi.pshell.core import CommandSource as CommandSource +from ch.psi.pshell.core import ContextAdapter as ContextListener +from ch.psi.pshell.core import Context +from ch.psi.pshell.core import InlineDevice as InlineDevice + +from ch.psi.pshell.data import DataSlice as DataSlice +from ch.psi.pshell.data import PlotDescriptor as PlotDescriptor +from ch.psi.pshell.data import Table as Table +from ch.psi.pshell.data import Provider as Provider +from ch.psi.pshell.data import ProviderHDF5 as ProviderHDF5 +from ch.psi.pshell.data import ProviderText as ProviderText +from ch.psi.pshell.data import ProviderCSV as ProviderCSV +from ch.psi.pshell.data import ProviderFDA as ProviderFDA +from ch.psi.pshell.data import Converter as DataConverter +from ch.psi.pshell.data import Layout as Layout +from ch.psi.pshell.data import LayoutBase as LayoutBase +from ch.psi.pshell.data import LayoutDefault as LayoutDefault +from ch.psi.pshell.data import LayoutTable as LayoutTable +from ch.psi.pshell.data import LayoutFDA as LayoutFDA +from ch.psi.pshell.data import LayoutSF as LayoutSF + +from ch.psi.pshell.device import Device as Device +from ch.psi.pshell.device import DeviceBase as DeviceBase +from ch.psi.pshell.device import DeviceConfig as DeviceConfig +from ch.psi.pshell.device import PositionerConfig as PositionerConfig +from ch.psi.pshell.device import RegisterConfig as RegisterConfig +from ch.psi.pshell.device import ReadonlyProcessVariableConfig as ReadonlyProcessVariableConfig +from ch.psi.pshell.device import ProcessVariableConfig as ProcessVariableConfig +from ch.psi.pshell.device import MotorConfig as MotorConfig +from ch.psi.pshell.device import Register as Register +from ch.psi.pshell.device import RegisterBase as RegisterBase +from ch.psi.pshell.device import ProcessVariableBase as ProcessVariableBase +from ch.psi.pshell.device import ControlledVariableBase as ControlledVariableBase +from ch.psi.pshell.device import PositionerBase as PositionerBase +from ch.psi.pshell.device import MasterPositioner as MasterPositioner +from ch.psi.pshell.device import MotorBase as MotorBase +from ch.psi.pshell.device import DiscretePositionerBase as DiscretePositionerBase +from ch.psi.pshell.device import MotorGroupBase as MotorGroupBase +from ch.psi.pshell.device import MotorGroupDiscretePositioner as MotorGroupDiscretePositioner +from ch.psi.pshell.device import ReadonlyRegisterBase as ReadonlyRegisterBase +from ch.psi.pshell.device import ReadonlyAsyncRegisterBase as ReadonlyAsyncRegisterBase +from ch.psi.pshell.device import Register as Register +RegisterArray = Register.RegisterArray +RegisterNumber = Register.RegisterNumber +RegisterBoolean = Register.RegisterBoolean +from ch.psi.pshell.device import RegisterCache as RegisterCache +from ch.psi.pshell.device import ReadonlyRegister +ReadonlyRegisterArray = ReadonlyRegister.ReadonlyRegisterArray +ReadonlyRegisterMatrix = ReadonlyRegister.ReadonlyRegisterMatrix +from ch.psi.pshell.device import DummyPositioner as DummyPositioner +from ch.psi.pshell.device import DummyMotor as DummyMotor +from ch.psi.pshell.device import DummyRegister as DummyRegister +from ch.psi.pshell.device import Timestamp as Timestamp +from ch.psi.pshell.device import Interlock as Interlock +from ch.psi.pshell.device import Readable as Readable +ReadableArray = Readable.ReadableArray +ReadableMatrix = Readable.ReadableMatrix +ReadableCalibratedArray = Readable.ReadableCalibratedArray +ReadableCalibratedMatrix = Readable.ReadableCalibratedMatrix +from ch.psi.pshell.device import ArrayCalibration as ArrayCalibration +from ch.psi.pshell.device import MatrixCalibration as MatrixCalibration +from ch.psi.pshell.device import Writable as Writable +WritableArray = Writable.WritableArray +from ch.psi.pshell.device import Stoppable as Stoppable +from ch.psi.pshell.device import Averager as Averager +from ch.psi.pshell.device import ArrayAverager as ArrayAverager +from ch.psi.pshell.device import Delta as Delta +from ch.psi.pshell.device import DeviceAdapter as DeviceListener +from ch.psi.pshell.device import ReadbackDeviceAdapter as ReadbackDeviceListener +from ch.psi.pshell.device import MotorAdapter as MotorListener +from ch.psi.pshell.device import MoveMode as MoveMode +from ch.psi.pshell.device import SettlingCondition as SettlingCondition +from ch.psi.pshell.device import HistogramGenerator as HistogramGenerator + +from ch.psi.pshell.epics import Epics as Epics +from ch.psi.pshell.epics import EpicsScan as EpicsScan +from ch.psi.pshell.epics import ChannelSettlingCondition as ChannelSettlingCondition +from ch.psi.pshell.epics import AreaDetector as AreaDetector +from ch.psi.pshell.epics import BinaryPositioner as BinaryPositioner +from ch.psi.pshell.epics import ChannelByte as ChannelByte +from ch.psi.pshell.epics import ChannelByteArray as ChannelByteArray +from ch.psi.pshell.epics import ChannelByteMatrix as ChannelByteMatrix +from ch.psi.pshell.epics import ChannelDouble as ChannelDouble +from ch.psi.pshell.epics import ChannelDoubleArray as ChannelDoubleArray +from ch.psi.pshell.epics import ChannelDoubleMatrix as ChannelDoubleMatrix +from ch.psi.pshell.epics import ChannelFloat as ChannelFloat +from ch.psi.pshell.epics import ChannelFloatArray as ChannelFloatArray +from ch.psi.pshell.epics import ChannelFloatMatrix as ChannelFloatMatrix +from ch.psi.pshell.epics import ChannelInteger as ChannelInteger +from ch.psi.pshell.epics import ChannelIntegerArray as ChannelIntegerArray +from ch.psi.pshell.epics import ChannelIntegerMatrix as ChannelIntegerMatrix +from ch.psi.pshell.epics import ChannelShort as ChannelShort +from ch.psi.pshell.epics import ChannelShortArray as ChannelShortArray +from ch.psi.pshell.epics import ChannelShortMatrix as ChannelShortMatrix +from ch.psi.pshell.epics import ChannelString as ChannelString +from ch.psi.pshell.epics import ControlledVariable as ControlledVariable +from ch.psi.pshell.epics import DiscretePositioner as DiscretePositioner +from ch.psi.pshell.epics import GenericChannel as GenericChannel +from ch.psi.pshell.epics import GenericArray as GenericArray +from ch.psi.pshell.epics import GenericMatrix as GenericMatrix +from ch.psi.pshell.epics import Manipulator as Manipulator +from ch.psi.pshell.epics import Motor as EpicsMotor +from ch.psi.pshell.epics import Positioner as Positioner +from ch.psi.pshell.epics import ProcessVariable as ProcessVariable +from ch.psi.pshell.epics import ReadonlyProcessVariable as ReadonlyProcessVariable +from ch.psi.pshell.epics import Scaler as Scaler +from ch.psi.pshell.epics import Scienta as Scienta +from ch.psi.pshell.epics import Slit as Slit +from ch.psi.pshell.epics import AreaDetectorSource as AreaDetectorSource +from ch.psi.pshell.epics import ArraySource as ArraySource +from ch.psi.pshell.epics import ByteArraySource as ByteArraySource +from ch.psi.pshell.epics import PsiCamera as PsiCamera +from ch.psi.pshell.epics import CAS as CAS + +from ch.psi.pshell.serial import SerialPortDevice as SerialPortDevice +from ch.psi.pshell.serial import TcpDevice as TcpDevice +from ch.psi.pshell.serial import UdpDevice as UdpDevice +from ch.psi.pshell.serial import SerialPortDeviceConfig as SerialPortDeviceConfig +from ch.psi.pshell.serial import SocketDeviceConfig as SocketDeviceConfig + +from ch.psi.pshell.modbus import ModbusTCP as ModbusTCP +from ch.psi.pshell.modbus import ModbusUDP as ModbusUDP +from ch.psi.pshell.modbus import ModbusSerial as ModbusSerial +from ch.psi.pshell.modbus import AnalogInput as ModbusAI +from ch.psi.pshell.modbus import AnalogInputArray as ModbusMAI +from ch.psi.pshell.modbus import AnalogOutput as ModbusAO +from ch.psi.pshell.modbus import AnalogOutputArray as ModbusMAO +from ch.psi.pshell.modbus import DigitalInput as ModbusDO +from ch.psi.pshell.modbus import DigitalInputArray as ModbusMDI +from ch.psi.pshell.modbus import DigitalOutput as ModbusDO +from ch.psi.pshell.modbus import DigitalOutputArray as ModbusMDO +from ch.psi.pshell.modbus import Register as ModbusReg +from ch.psi.pshell.modbus import ReadonlyProcessVariable as ModbusROPV +from ch.psi.pshell.modbus import ProcessVariable as ModbusPV +from ch.psi.pshell.modbus import ControlledVariable as ModbusCB +from ch.psi.pshell.modbus import ModbusDeviceConfig as ModbusDeviceConfig + +from ch.psi.pshell.imaging import Source as Source +from ch.psi.pshell.imaging import SourceBase as SourceBase +from ch.psi.pshell.imaging import DirectSource as DirectSource +from ch.psi.pshell.imaging import RegisterArraySource as RegisterArraySource +from ch.psi.pshell.imaging import RegisterMatrixSource as RegisterMatrixSource +from ch.psi.pshell.imaging import ImageListener as ImageListener +from ch.psi.pshell.imaging import ImageMeasurement as ImageMeasurement +from ch.psi.pshell.imaging import CameraSource as CameraSource +from ch.psi.pshell.imaging import ColormapAdapter as ColormapAdapter +from ch.psi.pshell.imaging import FileSource as FileSource +from ch.psi.pshell.imaging import MjpegSource as MjpegSource +from ch.psi.pshell.imaging import Webcam as Webcam +from ch.psi.pshell.imaging import Filter as Filter +from ch.psi.pshell.imaging import Utils as ImagingUtils +from ch.psi.pshell.imaging import Overlay as Overlay +from ch.psi.pshell.imaging import Overlays as Overlays +from ch.psi.pshell.imaging import Pen as Pen +from ch.psi.pshell.imaging import Data as Data +from ch.psi.pshell.imaging import Colormap as Colormap +from ch.psi.pshell.imaging import Renderer as Renderer + + +from ch.psi.pshell.plot import RangeSelectionPlot as RangeSelectionPlot +RangeSelectionPlotListener= RangeSelectionPlot.RangeSelectionPlotListener +from ch.psi.pshell.plot import LinePlot as LinePlot +from ch.psi.pshell.plot import MatrixPlot as MatrixPlot +from ch.psi.pshell.plot import TimePlot as TimePlot +from ch.psi.pshell.plot import SlicePlot as SlicePlot +from ch.psi.pshell.plot import LinePlotJFree as LinePlotJFree +from ch.psi.pshell.plot import MatrixPlotJFree as MatrixPlotJFree +from ch.psi.pshell.plot import TimePlotJFree as TimePlotJFree +from ch.psi.pshell.plot import SlicePlotDefault as SlicePlotDefault +from ch.psi.pshell.plot import LinePlotSeries as LinePlotSeries +from ch.psi.pshell.plot import LinePlotErrorSeries as LinePlotErrorSeries +from ch.psi.pshell.plot import MatrixPlotSeries as MatrixPlotSeries +from ch.psi.pshell.plot import TimePlotSeries as TimePlotSeries +from ch.psi.pshell.plot import SlicePlotSeries as SlicePlotSeries + +from ch.psi.pshell import scan as scans +from ch.psi.pshell.scan import ScanBase as ScanBase +from ch.psi.pshell.scan import ScanResult +from ch.psi.pshell.scan import Otf as Otf +from ch.psi.pshell.scan import ScanAbortedException as ScanAbortedException + +from ch.psi.pshell.crlogic import CrlogicPositioner as CrlogicPositioner +from ch.psi.pshell.crlogic import CrlogicSensor as CrlogicSensor + +from ch.psi.pshell.bs import BsScan +from ch.psi.pshell.bs import Stream as Stream +from ch.psi.pshell.bs import Provider as Provider +from ch.psi.pshell.bs import Dispatcher as Dispatcher +from ch.psi.pshell.bs import Scalar as Scalar +from ch.psi.pshell.bs import Waveform as Waveform +from ch.psi.pshell.bs import Matrix as Matrix +from ch.psi.pshell.bs import StreamCamera as StreamCamera +from ch.psi.pshell.bs import CameraServer as CameraServer +from ch.psi.pshell.bs import PipelineServer as PipelineServer +from ch.psi.pshell.bs import ProviderConfig as ProviderConfig +from ch.psi.pshell.bs import StreamConfig as StreamConfig +from ch.psi.pshell.bs import ScalarConfig as ScalarConfig +from ch.psi.pshell.bs import WaveformConfig as WaveformConfig +from ch.psi.pshell.bs import MatrixConfig as MatrixConfig + +from ch.psi.pshell.detector import DetectorConfig as DetectorConfig + +from ch.psi.pshell.ui import App as App + +from ch.psi.pshell.scripting import ViewPreference as Preference +from ch.psi.pshell.scripting import ScriptUtils as ScriptUtils +from ch.psi.pshell.device import Record +from javax.swing import SwingUtilities +invokeLater = SwingUtilities.invokeLater +invokeAndWait = SwingUtilities.invokeAndWait + +from org.jfree.ui import RectangleAnchor as RectangleAnchor +from org.jfree.ui import TextAnchor as TextAnchor + + +def string_to_obj(o): + if is_string(o): + o=str(o) + if "://" in o: + return InlineDevice(o) + ret = get_context().getInterpreterVariable(o) + if ret is None: + try: + return get_context().scriptManager.evalBackground(o).result + except: + return None + return ret + elif is_list(o): + ret = [] + for i in o: + ret.append(string_to_obj(i)) + return ret + return o + +def json_to_obj(o): + if is_string(o): + import json + return json.loads(o) + elif is_list(o): + ret = [] + for i in o: + ret.append(json_to_obj(i)) + return ret + return o + +################################################################################################### +#Scan classes +################################################################################################### + +def __no_args(f): + ret = f.func_code.co_argcount + return (ret-1) if type(f)==PyMethod else ret + +def __before_readout(scan, pos): + try: + if scan.before_read != None: + args = __no_args(scan.before_read) + if args==0: scan.before_read() + elif args==1: scan.before_read(pos.tolist()) + elif args==2: scan.before_read(pos.tolist(), scan) + except AttributeError: + pass + +def __after_readout(scan, record): + try: + if scan.after_read != None: + args = __no_args(scan.after_read) + if args==0: scan.after_read() + elif args==1: scan.after_read(record) + elif args==2: scan.after_read(record, scan) + except AttributeError: + pass + +def __before_pass(scan, num_pass): + try: + if scan.before_pass != None: + args = __no_args(scan.before_pass) + if args==0:scan.before_pass() + elif args==1:scan.before_pass(num_pass) + elif args==2:scan.before_pass(num_pass, scan) + except AttributeError: + pass + +def __after_pass(scan, num_pass): + try: + if scan.after_pass != None: + args = __no_args(scan.after_pass) + if args==0:scan.after_pass() + elif args==1:scan.after_pass(num_pass) + elif args==2:scan.after_pass(num_pass, scan) + except AttributeError: + pass + +def __before_region(scan, num_region): + try: + if scan.before_region != None: + args = __no_args(scan.before_region) + if args==0:scan.before_region() + elif args==1:scan.before_region(num_region) + elif args==2:scan.before_region(num_region, scan) + except AttributeError: + pass + +#TODO +LineScan=scans.LineScan +AreaScan=scans.AreaScan +RegionScan=scans.RegionScan +VectorScan=scans.VectorScan +ContinuousScan=scans.ContinuousScan +TimeScan=scans.TimeScan +MonitorScan=scans.MonitorScan +ManualScan=scans.ManualScan +BinarySearch=scans.BinarySearch +HillClimbingSearcharySearch=scans.HillClimbingSearch + +""" +class LineScan(scans.LineScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + +class ContinuousScan(scans.ContinuousScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + +class AreaScan(scans.AreaScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + +class RegionScan(scans.RegionScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + def onBeforeRegion(self, num): __before_region(self,num) + +class VectorScan(scans.VectorScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + +class ContinuousScan(scans.ContinuousScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + +class TimeScan(scans.TimeScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + +class MonitorScan(scans.MonitorScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + +class BsScan(scans.BsScan): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + def onBeforePass(self, num): __before_pass(self, num) + def onAfterPass(self, num): __after_pass(self, num) + +class ManualScan(scans.ManualScan): + def __init__(self, writables, readables, start = None, end = None, steps = None, relative = False, dimensions = None): + ManualScan.__init__(self, writables, readables, start, end, steps, relative) + self._dimensions = dimensions + + def append(self,setpoints, positions, values, timestamps=None): + ManualScan.append(self, to_array(setpoints), to_array(positions), to_array(values), None if (timestamps is None) else to_array(timestamps)) + + def getDimensions(self): + if self._dimensions == None: + return ManualScan.getDimensions(self) + else: + return self._dimensions + +class BinarySearch(scans.BinarySearch): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) + +class HillClimbingSearch(scans.HillClimbingSearch): + def onBeforeReadout(self, pos): __before_readout(self, pos) + def onAfterReadout(self, rec): __after_readout(self, rec) +""" + +def processScanPars(scan, pars): + #TODO + #scan.before_read = pars.pop("before_read",None) + #scan.after_read = pars.pop("after_read",None) + #scan.before_pass = pars.pop("before_pass",None) + #scan.after_pass = pars.pop("after_pass",None) + #scan.before_region= pars.pop("before_region",None) + scan.setPlotTitle(pars.pop("title",None)) + scan.setHidden(pars.pop("hidden",False)) + scan.setSettleTimeout (pars.pop("settle_timeout",ScanBase.getScansSettleTimeout())) + scan.setUseWritableReadback (pars.pop("use_readback",ScanBase.getScansUseWritableReadback())) + scan.setInitialMove(pars.pop("initial_move",ScanBase.getScansTriggerInitialMove())) + scan.setParallelPositioning(pars.pop("parallel_positioning",ScanBase.getScansParallelPositioning())) + scan.setAbortOnReadableError(pars.pop("abort_on_error",ScanBase.getAbortScansOnReadableError())) + scan.setRestorePosition (pars.pop("restore_position",ScanBase.getRestorePositionOnRelativeScans())) + scan.setCheckPositions(pars.pop("check_positions",ScanBase.getScansCheckPositions())) + scan.setMonitors(to_list(string_to_obj(pars.pop("monitors",None)))) + scan.setSnaps(to_list(string_to_obj(pars.pop("snaps",None)))) + scan.setDiags(to_list(string_to_obj(pars.pop("diags",None)))) + scan.setMeta(pars.pop("meta",None)) + get_context().setCommandPars(scan, pars) + + + +################################################################################################### +#Simple EPICS Channel abstraction +################################################################################################### + +def create_channel(name, type=None, size=None): + return Epics.newChannel(name, Epics.getChannelType(type), size) + + +#Not using finalizer: closing channels in garbage collection generate errors +class Channel(): #TODO (PropertyChangeListener, Writable, Readable, DeviceBase): + def __init__(self, channel_name, type = None, size = None, callback=None, alias = None, monitored=None, name = None): + """ Create an object that encapsulates an Epics PV connection. + Args: + channel_name(str):name of the channel + type(str, optional): type of PV. By default gets the PV standard field type. + Scalar values: 'b', 'i', 'l', 'd', 's'. + Array values: '[b', '[i,', '[l', '[d', '[s'. + size(int, optional): the size of the channel + callback(function, optional): The monitor callback. + alias(str): name to be used on scans. + """ + super(DeviceBase, self).__init__(name if (name is not None) else channel_name.replace(":","_").replace(".","_")) + self.channel = create_channel(channel_name, type, size) + self.callback = callback + self._alias = alias + if monitored is not None:self.setMonitored(monitored) + self.initialize() + + def get_channel_name(self): + """Return the name of the channel. + """ + return self.channel.getName() + + def get_size(self): + """Return the size of the channel. + """ + return self.channel.size + + def set_size(self, size): + """Set the size of the channel. + """ + self.channel.size = size + + def is_connected(self): + """Return True if channel is connected. + """ + return self.channel.connected + + def doSetMonitored(self, value): + self.channel.monitored = value + if (value): + self.channel.addPropertyChangeListener(self) + else: + self.channel.removePropertyChangeListener(self) + + + def is_monitored(self): + """Return True if channel is monitored + """ + return self.channel.monitored + + def set_monitored(self, value): + """Set a channel monitor to trigger the callback function defined in the constructor. + """ + self.setMonitored(value) + + def propertyChange(self, pce): + if pce.getPropertyName() == "value": + value=pce.getNewValue() + self.setCache(value, None) + if self.callback is not None: + self.callback(value) + + def put(self, value, timeout=None): + """Write to channel and wait value change. In the case of a timeout throws a TimeoutException. + Args: + value(obj): value to be written + timeout(float, optional): timeout in seconds. If none waits forever. + """ + if (timeout==None): + self.channel.setValue(value) + else: + self.channel.setValueAsync(value).get(int(timeout*1000), java.util.concurrent.TimeUnit.MILLISECONDS) + self.setCache(value, None) + + def putq(self, value): + """Write to channel and don't wait. + """ + self.channel.setValueNoWait(value) + + def get(self, force = False): + """Get channel value. + """ + ret = self.channel.getValue(force) + self.setCache(ret, None) + return ret + + def wait_for_value(self, value, timeout=None, comparator=None): + """Wait channel to reach a value, using a given comparator. In the case of a timeout throws a TimeoutException. + Args: + value(obj): value to be verified. + timeout(float, optional): timeout in seconds. If None waits forever. + comparator (java.util.Comparator, optional). If None, uses Object.equals. + """ + if comparator is None: + if timeout is None: + self.channel.waitForValue(value) + else: + self.channel.waitForValue(value, int(timeout*1000)) + self.setCache(value, None) + else: + if timeout is None: + self.channel.waitForValue(value, comparator) + else: + self.channel.waitForValue(value, comparator, int(timeout*1000)) + + def doUpdate(self): + self.get() + + def close(self): + """Close the channel. + """ + Epics.closeChannel(self.channel) + + def setAlias(self, alias): + self._alias = alias + + def getAlias(self): + return self._alias if self._alias else self.getName() + + def write(self, value): + self.put(value) + + def read(self): + return self.get() + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + +print("Startup") + +################################################################################################### +#Default empty callbacks +################################################################################################### +def on_command_started(info): pass +def on_command_finished(info): pass +def on_session_started(id): pass +def on_session_finished(id): pass +def on_change_data_path(path): pass + + +################################################################################################### +#Help and access to function documentation +################################################################################################### +def _getBuiltinFunctions(filter = None): + ret = [] + for name in globals().keys(): + val = globals()[name] + if isinstance(val, types.FunctionType): + if filter is None or filter in name: + #Only "public" documented functions + if not name.startswith('_') and (val.__doc__ is not None): + ret.append(val) + return to_array(ret) + + +def _getBuiltinFunctionNames(filter = None): + ret = [] + for function in _getBuiltinFunctions(filter): + ret.append(function.__name__) + return to_array(ret) + +def _getFunctionDoc(function): + if is_string(function): + if function not in globals(): + return + function = globals()[function] + if isinstance(function, types.FunctionType): + if '__doc__' in dir(function): + return function.__name__ + str(signature(function)) + "\n\n" + function.__doc__ + +def help(object = None): + """ + Print help message for function or object (if available). + + Args: + object (any, optional): function or object to get help. + If null prints a list of the builtin functions. + + Returns: + None + """ + if object is None: + print ("Built-in functions:") + for f in _getBuiltinFunctionNames(): + print ("\t" + f) + else: + if isinstance(object, types.FunctionType): + print (_getFunctionDoc(object)) + elif '__doc__' in dir(object): + print (object.__doc__) + +################################################################################################### +#Variable injection +################################################################################################### + +def _get_caller(): + #Not doing inspect.currentframe().f_back because inspect is slow to load + return sys._getframe(1).f_back if hasattr(sys, "_getframe") else None + +def inject(): + """Restore initial globals: re-inject devices and startup variables to the interpreter. + + Args: + None + + Returns: + None + """ + if __name__ == "__main__": + get_context().injectVars() + else: + _get_caller().f_globals.update(get_context().scriptManager.injections) + + +################################################################################################### +#Script evaluation and return values +################################################################################################### + +def run(script_name, args = None, locals = None): + """Run script: can be absolute path, relative, or short name to be search in the path. + Args: + args(Dict ot List): Sets sys.argv (if list) or gobal variables(if dict) to the script. + locals(Dict): If not none sets the locals()for the runing script. + If locals is used then script definitions will not go to global namespace. + + Returns: + The script return value (if set with set_return) + """ + script = get_context().scriptManager.library.resolveFile(script_name) + if script is not None and os.path.isfile(script): + info = get_context().startScriptExecution(script_name, args) + try: + set_return(None) + if args is not None: + if isinstance(args,list) or isinstance(args,tuple): + sys.argv = list(args) + globals()["args"] = sys.argv + else: + for arg in args.keys(): + globals()[arg] = args[arg] + if (locals is None): + execfile(script, globals()) + else: + execfile(script, globals(), locals) + ret = get_return() + get_context().finishScriptExecution(info, ret) + return ret + except Exception as ex: + get_context().finishScriptExecution(info, ex) + raise ex + raise IOError("Invalid script: " + str(script_name)) + +def abort(): + """Abort the execution of ongoing task. It can be called from the script to quit. + + Args: + None + + Returns: + None + """ + fork(get_context().abort) #Cannot be on script execution thread + while True: sleep(10.0) + +def set_return(value): + """Sets the script return value. This value is returned by the "run" function. + + Args: + value(Object): script return value. + + Returns: + None + """ + global __THREAD_EXEC_RESULT__ + __THREAD_EXEC_RESULT__=value + return value #Used when parsing file + +def get_return(): + if __name__ == "__main__": + global __THREAD_EXEC_RESULT__ + return __THREAD_EXEC_RESULT__ + else: + return _get_caller().f_globals["__THREAD_EXEC_RESULT__"] + + + +################################################################################################### +#Builtin functions +################################################################################################### + + +################################################################################################### +#Scan commands +################################################################################################### + +def lscan(writables, readables, start, end, steps, latency=0.0, relative=False, passes=1, zigzag=False, **pars): + """Line Scan: positioners change together, linearly from start to end positions. + + Args: + writables(list of Writable): Positioners set on each step. + readables(list of Readable): Sensors to be sampled on each step. + start(list of float): start positions of writables. + end(list of float): final positions of writables. + steps(int or float or list of float): number of scan steps (int) or step size (float). + relative (bool, optional): if true, start and end positions are relative to current. + latency(float, optional): settling time for each step before readout, defaults to 0.0. + passes(int, optional): number of passes + zigzag(bool, optional): if true writables invert direction on each pass. + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - hidden(bool, optional): if true generates no effects on user interface. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - before_pass (function(pass_num, scan), optional): called before each pass. + - after_pass (function(pass_num, scan), optional): callback after each pass. + - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever. + - initial_move (bool, optional): if true (default) perform move to initial position prior to scan start. + - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - restore_position (bool, optional): if true (default) then restore initial position after relative scans. + - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - diags (list of Readable, optional): diagnostic device values are saved at each scan point. + - meta (dict, optional): scan metadata. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + latency_ms=int(latency*1000) + #writables=to_list(string_to_obj(writables)) + readables=to_list(string_to_obj(readables)) + #start=to_list(start) + #end=to_list(end) + if type(steps) is float or is_list(steps): + steps = to_list(steps) + scan = LineScan(writables,readables, start, end , steps, relative, latency_ms, int(passes), zigzag) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def vscan(writables, readables, vector, line = False, latency=0.0, relative=False, passes=1, zigzag=False, **pars): + """Vector Scan: positioner values provided in a vector. + + Args: + writables(list of Writable): Positioners set on each step. + readables(list of Readable): Sensors to be sampled on each step. + vector (generator (floats or lists of float) or list of list of float): positioner values. + line (bool, optional): if true, processs as line scan (1d) + relative (bool, optional): if true, start and end positions are relative to current. + latency(float, optional): settling time for each step before readout, defaults to 0.0. + passes(int, optional): number of passes (disregarded if vector is a generator). + zigzag(bool, optional): if true writables invert direction on each pass (disregarded if vector is a generator). + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - before_pass (function(pass_num, scan), optional): called before each pass. + - after_pass (function(pass_num, scan), optional): callback after each pass. + - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever. + - initial_move (bool, optional): if true (default) perform move to initial position prior to scan start. + - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - restore_position (bool, optional): if true (default) then restore initial position after relative scans. + - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - diags (list of Readable, optional): diagnostic device values are saved at each scan point. + - meta (dict, optional): scan metadata. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + latency_ms=int(latency*1000) + writables=to_list(string_to_obj(writables)) + readables=to_list(string_to_obj(readables)) + if type (vector) == PyGenerator: + scan = VectorScan(writables,readables, vector, line, relative, latency_ms) + else: + if len(vector) == 0: + vector.append([]) + elif (not is_list(vector[0])) and (not is_array(vector[0])): + vector = [[x,] for x in vector] + vector = to_array(vector, 'd') + scan = VectorScan(writables,readables, vector, line, relative, latency_ms, int(passes), zigzag) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def ascan(writables, readables, start, end, steps, latency=0.0, relative=False, passes=1, zigzag=False, **pars): + """Area Scan: multi-dimentional scan, each positioner is a dimention. + + Args: + writables(list of Writable): Positioners set on each step. + readables(list of Readable): Sensors to be sampled on each step. + start(list of float): start positions of writables. + end(list of float): final positions of writables. + steps(list of int or list of float): number of scan steps (int) or step size (float). + latency(float, optional): settling time for each step before readout, defaults to 0.0. + relative (bool, optional): if true, start and end positions are relative to current. + passes(int, optional): number of passes + zigzag (bool, optional): if true writables invert direction on each row. + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - before_pass (function(pass_num, scan), optional): called before each pass. + - after_pass (function(pass_num, scan), optional): callback after each pass. + - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever. + - initial_move (bool, optional): if true (default) perform move to initial position prior to scan start. + - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - restore_position (bool, optional): if true (default) then restore initial position after relative scans. + - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - diags (list of Readable, optional): diagnostic device values are saved at each scan point. + - meta (dict, optional): scan metadata. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + latency_ms=int(latency*1000) + writables=to_list(string_to_obj(writables)) + readables=to_list(string_to_obj(readables)) + start=to_list(start) + end=to_list(end) + if is_list(steps): + steps = to_list(steps) + scan = AreaScan(writables,readables, start, end , steps, relative, latency_ms, int(passes), zigzag) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + + +def rscan(writable, readables, regions, latency=0.0, relative=False, passes=1, zigzag=False, **pars): + """Region Scan: positioner scanned linearly, from start to end positions, in multiple regions. + + Args: + writable(Writable): Positioner set on each step, for each region. + readables(list of Readable): Sensors to be sampled on each step. + regions (list of tuples (float,float, int) or (float,float, float)): each tuple define a scan region + (start, stop, steps) or (start, stop, step_size) + relative (bool, optional): if true, start and end positions are relative to current. + latency(float, optional): settling time for each step before readout, defaults to 0.0. + passes(int, optional): number of passes + zigzag(bool, optional): if true writable invert direction on each pass. + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - before_pass (function(pass_num, scan), optional): called before each pass. + - after_pass (function(pass_num, scan), optional): callback after each pass. + - before_region (function(region_num, scan), optional): callback before entering a region. + - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever. + - initial_move (bool, optional): if true (default) perform move to initial position prior to scan start. + - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - restore_position (bool, optional): if true (default) then restore initial position after relative scans. + - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - diags (list of Readable, optional): diagnostic device values are saved at each scan point. + - meta (dict, optional): scan metadata. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + start=[] + end=[] + steps=[] + for region in regions: + start.append(region[0]) + end.append(region[1]) + steps.append(region[2]) + latency_ms=int(latency*1000) + writable=string_to_obj(writable) + readables=to_list(string_to_obj(readables)) + start=to_list(start) + end=to_list(end) + steps = to_list(steps) + scan = RegionScan(writable,readables, start, end , steps, relative, latency_ms, int(passes), zigzag) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def cscan(writables, readables, start, end, steps, latency=0.0, time=None, relative=False, passes=1, zigzag=False, **pars): + """Continuous Scan: positioner change continuously from start to end position and readables are sampled on the fly. + + Args: + writable(Speedable or list of Motor): A positioner with a getSpeed method or + a list of motors. + readables(list of Readable): Sensors to be sampled on each step. + start(float or list of float): start positions of writables. + end(float or list of float): final positions of writabless. + steps(int or float or list of float): number of scan steps (int) or step size (float). + latency(float, optional): sleep time in each step before readout, defaults to 0.0. + time (float, seconds): if not None then speeds are set according to time. + relative (bool, optional): if true, start and end positions are relative to current. + passes(int, optional): number of passes + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - before_pass (function(pass_num, scan), optional): called before each pass. + - after_pass (function(pass_num, scan), optional): callback after each pass. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - restore_position (bool, optional): if true (default) then restore initial position after relative scans. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - diags (list of Readable, optional): diagnostic device values are saved at each scan point. + - meta (dict, optional): scan metadata. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + latency_ms=int(latency*1000) + readables=to_list(string_to_obj(readables)) + writables=to_list(string_to_obj(writables)) + start=to_list(start) + end=to_list(end) + #A single Writable with fixed speed + if time is None: + if is_list(steps): steps=steps[0] + scan = ContinuousScan(writables[0],readables, start[0], end[0] , steps, relative, latency_ms, int(passes), zigzag) + #A set of Writables with speed configurable + else: + if type(steps) is float or is_list(steps): + steps = to_list(steps) + scan = ContinuousScan(writables,readables, start, end , steps, time, relative, latency_ms, int(passes), zigzag) + + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def hscan(config, writable, readables, start, end, steps, passes=1, zigzag=False, **pars): + """Hardware Scan: values sampled by external hardware and received asynchronously. + + Args: + config(dict): Configuration of the hardware scan. The "class" key provides the implementation class. + Other keys are implementation specific. + writable(Writable): A positioner appropriated to the hardware scan type. + readables(list of Readable): Sensors appropriated to the hardware scan type. + start(float): start positions of writable. + end(float): final positions of writables. + steps(int or float): number of scan steps (int) or step size (float). + passes(int, optional): number of passes + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - after_read (function(record, scan), optional): callback on each step, after sampling. + - before_pass (function(pass_num, scan), optional): callback before each scan pass execution. + - after_pass (function(pass_num, scan), optional): callback after each scan pass execution. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - meta (dict, optional): scan metadata. + + Returns: + ScanResult. + """ + cls = Class.forName(config["class"]) + class HardwareScan(cls): + def __init__(self, config, writable, readables, start, end, stepSize, passes, zigzag): + cls.__init__(self, config, writable, readables, start, end, stepSize, passes, zigzag) + def onAfterReadout(self, record): + __after_readout(self, record) + def onBeforePass(self, num_pass): + __before_pass(self, num_pass) + def onAfterPass(self, num_pass): + __after_pass(self, num_pass) + + readables=to_list(string_to_obj(readables)) + scan = HardwareScan(config, writable,readables, start, end , steps, int(passes), zigzag) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def bscan(stream, records, timeout = None, passes=1, **pars): + """BS Scan: records all values in a beam synchronous stream. + + Args: + stream(Stream): stream object or list of chanel names to build stream from + records(int): number of records to store + timeout(float, optional): maximum scan time in seconds. + passes(int, optional): number of passes + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - before_pass (function(pass_num, scan), optional): called before each pass. + - after_pass (function(pass_num, scan), optional): callback after each pass. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - diags (list of Readable, optional): diagnostic device values are saved at each scan point. + - meta (dict, optional): scan metadata. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + timeout_ms=int(timeout*1000) if ((timeout is not None) and (timeout>=0)) else -1 + if not is_list(stream): + stream=string_to_obj(stream) + scan = BsScan(stream,int(records), timeout_ms, int(passes)) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def tscan(readables, points, interval, passes=1, fixed_rate=True, **pars): + """Time Scan: sensors are sampled in fixed time intervals. + + Args: + readables(list of Readable): Sensors to be sampled on each step. + points(int): number of samples. + interval(float): time interval between readouts. Minimum temporization is 0.001s + passes(int, optional): number of passes + fixed_rate(bool, optional): in the case of delays in sampling: + If True tries to preserve to total scan time, accelerating following sampling. + If False preserves the interval between samples, increasing scan time. + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - before_pass (function(pass_num, scan), optional): called before each pass. + - after_pass (function(pass_num, scan), optional): callback after each pass. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - diags (list of Readable, optional): diagnostic device values are saved at each scan point. + - meta (dict, optional): scan metadata. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + interval= max(interval, 0.001) #Minimum temporization is 1ms + interval_ms=int(interval*1000) + readables=to_list(string_to_obj(readables)) + scan = TimeScan(readables, points, interval_ms, int(passes), bool(fixed_rate)) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def mscan(trigger, readables, points=-1, timeout=None, asynchronous=True, take_initial=False, passes=1, **pars): + """Monitor Scan: sensors are sampled when received change event of the trigger device. + + Args: + trigger(Device or list of Device): Source of the sampling triggering. + readables(list of Readable): Sensors to be sampled on each step. + If trigger has cache and is included in readables, it is not read + for each step, but the change event value is used. + points(int, optional): number of samples (-1 for undefined). + timeout(float, optional): maximum scan time in seconds (None for no timeout). + asynchronous(bool, optional): if True then records are sampled and stored on event change callback. Enforce + reading only cached values of sensors. + If False, the scan execution loop waits for trigger cache update. Do not make + cache only access, but may loose change events. + take_initial(bool, optional): if True include current values as first record (before first trigger). + passes(int, optional): number of passes + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - before_pass (function(pass_num, scan), optional): called before each pass. + - after_pass (function(pass_num, scan), optional): callback after each pass. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - monitors (list of Device, optional): device values are saved on every change event during the scan. + - snaps (list of Readable, optional): snapshot device values are saved before the scan. + - diags (list of Readable, optional): diagnostic device values are saved at each scan point. + - meta (dict, optional): scan metadata. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + timeout_ms=int(timeout*1000) if ((timeout is not None) and (timeout>=0)) else -1 + trigger = string_to_obj(trigger) + readables=to_list(string_to_obj(readables)) + scan = MonitorScan(trigger, readables, points, timeout_ms, asynchronous, take_initial, int(passes)) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def escan(name, **pars): + """Epics Scan: execute an Epics Scan Record. + + Args: + name(str): Name of scan record. + title(str, optional): plotting window name. + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - Aditional arguments defined by set_exec_pars. + + Returns: + ScanResult. + """ + scan = EpicsScan(name) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + + +def bsearch(writables, readable, start, end, steps, maximum = True, strategy = "Normal", latency=0.0, relative=False, **pars): + """Binary search: searches writables in a binary search fashion to find a local maximum for the readable. + + Args: + writables(list of Writable): Positioners set on each step. + readable(Readable): Sensor to be sampled. + start(list of float): start positions of writables. + end(list of float): final positions of writables. + steps(float or list of float): resolution of search for each writable. + maximum (bool , optional): if True (default) search maximum, otherwise minimum. + strategy (str , optional): "Normal": starts search midway to scan range and advance in the best direction. + Uses orthogonal neighborhood (4-neighborhood for 2d) + "Boundary": starts search on scan range. + "FullNeighborhood": Uses complete neighborhood (8-neighborhood for 2d) + + latency(float, optional): settling time for each step before readout, defaults to 0.0. + relative (bool, optional): if true, start and end positions are relative to current. + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever. + - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - restore_position (bool, optional): if true (default) then restore initial position after relative scans. + - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes. + - Aditional arguments defined by set_exec_pars. + + Returns: + SearchResult. + """ + latency_ms=int(latency*1000) + writables=to_list(string_to_obj(writables)) + readable=string_to_obj(readable) + start=to_list(start) + end=to_list(end) + steps = to_list(steps) + strategy = BinarySearch.Strategy.valueOf(strategy) + scan = BinarySearch(writables,readable, start, end , steps, maximum, strategy, relative, latency_ms) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + +def hsearch(writables, readable, range_min, range_max, initial_step, resolution, filter=1, maximum=True, latency=0.0, relative=False, **pars): + """Hill Climbing search: searches writables in decreasing steps to find a local maximum for the readable. + Args: + writables(list of Writable): Positioners set on each step. + readable(Readable): Sensor to be sampled. + range_min(list of float): minimum positions of writables. + range_max(list of float): maximum positions of writables. + initial_step(float or list of float):initial step size for for each writable. + resolution(float or list of float): resolution of search for each writable (minimum step size). + filter(int): number of aditional steps to filter noise + maximum (bool , optional): if True (default) search maximum, otherwise minimum. + latency(float, optional): settling time for each step before readout, defaults to 0.0. + relative (bool, optional): if true, start and end positions are relative to current. + pars(keyworded variable length arguments, optional): scan optional named arguments: + - title(str, optional): plotting window name. + - before_read (function(positions, scan), optional): called on each step, before sampling. + - after_read (function(record, scan), optional): called on each step, after sampling. + - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever. + - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel. + - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false. + - restore_position (bool, optional): if true (default) then restore initial position after relative scans. + - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes. + - Aditional arguments defined by set_exec_pars. + + Returns: + SearchResult. + """ + latency_ms=int(latency*1000) + writables=to_list(string_to_obj(writables)) + readable=string_to_obj(readable) + range_min=to_list(range_min) + range_max=to_list(range_max) + initial_step = to_list(initial_step) + resolution = to_list(resolution) + scan = HillClimbingSearch(writables,readable, range_min, range_max , initial_step, resolution, filter, maximum, relative, latency_ms) + processScanPars(scan, pars) + scan.start() + return scan.getResult() + + +################################################################################################### +#Data plotting +################################################################################################### + +def plot(data, name = None, xdata = None, ydata=None, title=None): + """Request one or multiple plots of user data (1d, 2d or 3d). + + Args: + data: array or list of values. For multiple plots, list of arrays. + name(str or list of str, optional): plot name. For multiple plots, list of names. + xdata: array or list of values. For multiple plots, list of arrays. + ydata: array or list of values. For multiple plots, list of arrays. + title(str, optional): plotting window name. + + Returns: + ArrayList of Plot. + """ + data = json_to_obj(data) + xdata = json_to_obj(xdata) + ydata = json_to_obj(ydata) + if is_java_instance(data, Table): + if is_list(xdata): + xdata = to_array(xdata, 'd') + return get_context().plot(data,xdata,name,title) + + if is_java_instance(data, ScanResult): + return get_context().plot(data,title) + + if (name is not None) and is_list(name): + if len(name)==0: + name=None; + else: + if (data==None): + data = [] + for n in name: + data.append([]) + plots = reflect.Array.newInstance(Class.forName("ch.psi.pshell.data.PlotDescriptor"), len(data)) + for i in range (len(data)): + plotName = None if (name is None) else name[i] + x = xdata + if is_list(x) and len(x)>0 and (is_list(x[i]) or is_java_instance(x[i] , List) or is_array(x[i])): + x = x[i] + y = ydata + if is_list(y) and len(y)>0 and (is_list(y[i]) or is_java_instance(y[i] , List) or is_array(y[i])): + y = y[i] + plots[i] = PlotDescriptor(plotName , to_array(data[i], 'd'), to_array(x, 'd'), to_array(y, 'd')) + return get_context().plot(plots,title) + else: + plot = PlotDescriptor(name, to_array(data, 'd'), to_array(xdata, 'd'), to_array(ydata, 'd')) + return get_context().plot(plot,title) + +def get_plots(title=None): + """Return all current plots in the plotting window given by 'title'. + + Args: + title(str, optional): plotting window name. + + Returns: + ArrayList of Plot. + """ + return get_context().getPlots(title) + +def get_plot_snapshots(title = None, file_type = "png", size = None, temp_path = get_context().getSetup().getContextPath()): + """Returns list with file names of plots snapshots from a plotting context. + + Args: + title(str, optional): plotting window name. + file_type(str, optional): "png", "jpg", "bmp" or "gif" + size(array, optional): [width, height] + temp_path(str, optional): path where the files will be generated. + + Returns: + list of strings + """ + time.sleep(0.1) #Give some time to plot to be finished - it is not sync with acquisition + ret = [] + if size != None: + size = Dimension(size[0], size[1]) + plots = get_plots(title) + for i in range(len(plots)): + p = plots[i] + name = p.getTitle() + if name is None or name == "": + name = str(i) + file_name = os.path.abspath(temp_path + "/" + name + "." + file_type) + p.saveSnapshot(file_name , file_type, size) + ret.append(file_name) + return ret + + +################################################################################################### +#Data access +################################################################################################### + +def load_data(path, index=0, shape=None, root=None): + """Read data from the current persistence context or from data files. + + Args: + path(str): Path to group or dataset relative to the root. + If path is in the format 'root|path', or else if 'root' is defined, then + reads from data file given by root. Otherwise uses current data persistence file. + root(str, optional): data file. + index(int or list, optional): + if integer, data depth (used for 3D datasets returning a 2d matrix) + If a list, specifies the full coordinate for multidimensional datasets. + shape(list, optional): only valid if index is a list, provides the shape of the data array. + In this case return a flattened a one-dimensional array. + + Returns: + Data array + """ + dm=get_context().getDataManager() + if index is not None and is_list(index): + slice = dm.getData(path, index, shape) if (root==None) else dm.getData(root, path, index, shape) + else: + slice = dm.getData(path, index) if (root==None) else dm.getData(root, path, index) + return slice.sliceData + +def get_attributes(path, root=None): + """Get the attributes from group or dataset. + + Args: + path(str): Path to group or dataset relative to the root. + If path is in the format 'root|path', or else if 'root' is defined, then + reads from data file given by root. Otherwise uses current data persistence file. + root(str, optional): data file. + Returns: + Dictionary + """ + if (root is None): + return get_context().getDataManager().getAttributes(path) + return get_context().getDataManager().getAttributes(root, path) + +def get_data_info(path, root=None): + """Get information about the group or dataset. + + Args: + path(str): Path to group or dataset relative to the current persistence context root. + If path is in the format 'root|path', or else if 'root' is defined, then + reads from data file given by root. Otherwise uses current data persistence file. + root(str, optional): data file. + Returns: + Dictionary + """ + if (root is None): + return get_context().getDataManager().getInfo(path) + return get_context().getDataManager().getInfo(root, path) + +def save_dataset(path, data, type='d', unsigned=False, features=None): + """Save data into a dataset within the current persistence context. + + Args: + path(str): Path to dataset relative to the current persistence context root. + type(str, optional): array type - 'd'=double (default), 'b'=byte, 'h'=short, 'i'=int, + 'l'=long, 'f'=float, 'c'=char, 's'=String, 'z'=bool, 'o'=Object + data (array or list): data to be saved + unsigned(boolean, optional): create a dataset of unsigned type. + features(dictionary, optional): See create_dataset. + + Returns: + Dictionary + """ + data = to_array(data, type) + get_context().getDataManager().setDataset(path, data, unsigned, features) + +def create_group(path): + """Create an empty dataset within the current persistence context. + + Args: + path(str): Path to group relative to the current persistence context root. + Returns: + None + """ + get_context().getDataManager().createGroup(path) + +def create_dataset(path, type, unsigned=False, dimensions=None, features=None): + """Create an empty dataset within the current persistence context. + + Args: + path(str): Path to dataset relative to the current persistence context root. + type(str): array type 'b' = byte, 'h' = short, 'i' = int, 'l' = long, 'f' = float, + 'd' = double, 'c' = char, 's' = String, 'z'=bool, 'o' = Object + unsigned(boolean, optional) + dimensions(tuple of int, optional): a 0 value means variable length in that dimension. + features(dictionary, optional): storage features for the dataset, format specific. + Keys for HDF5: "layout": "compact", "contiguous" or "chunked" + "compression": True, "max" or deflation level from 1 to 9 + "shuffle": Byte shuffle before compressing. + "chunk": tuple, setting the chunk size + Default: No compression, contiguous for fixed size arrays, chunked for variable size, compact for scalars. + Returns: + None + """ + get_context().getDataManager().createDataset(path, ScriptUtils.getType(type), unsigned, dimensions, features) + +def create_table(path, names, types=None, lengths=None, features=None): + """Create an empty table (dataset of compound type) within the current persistence context. + + Args: + path(str): Path to dataset relative to the current persistence context root. + names(list of strings): name of each column + types(array of str): 'b' = byte, 'h' = short, 'i' = int, 'l' = long, 'f' = float, + 'd' = double, 'c' = char, 's' = String, 'o' = Object + Note:A '[' prefix on type name indicates an array type. + lengths(list of int): the array length for each columns(0 for scalar types). + features(dictionary, optional): See create_dataset. + Returns: + None + """ + type_classes = [] + if (types is not None): + for i in range (len(types)): + type_classes.append(ScriptUtils.getType(types[i])) + get_context().getDataManager().createDataset(path, names, type_classes, lengths, features) + +def append_dataset(path, data, index=None, type='d', shape=None): + """Append data to dataset. + + Args: + path(str): Path to dataset relative to the current persistence context root. + data(number or array or list): name of each column. + index(int or list, optional): if set then add the data in a specific position in the dataset. + If integer is the index in an array (data must be 1 order lower than dataset) + If a list, specifies the full coordinate for multidimensional datasets. + type(str, optional): array type 'b' = byte, 'h' = short, 'i' = int, 'l' = long, 'f' = float, + 'd' = double, 'c' = char, 's' = String, 'o' = Object + default: 'd' (convert data to array of doubles) + shape(list, optional): only valid if index is a list, provides the shape of the data array. + In this case data must be a flattened one-dimensional array. + Returns: + None + """ + data = to_array(data, type) + if index is None: + get_context().getDataManager().appendItem(path, data) + else: + if is_list(index): + if shape is None: + shape = [len(index)] + get_context().getDataManager().setItem(path, data, index, shape) + else: + get_context().getDataManager().setItem(path, data, index) + +def append_table(path, data): + """Append data to a table (dataset of compound type) + + Args: + path(str): Path to dataset relative to the current persistence context root. + data(list): List of valus for each column of the table. + Returns: + None + """ + if is_list(data): + arr = reflect.Array.newInstance(Class.forName("java.lang.Object"),len(data)) + for i in range (len(data)): + if is_list(data[i]): + arr[i] = to_array(data[i], 'd') + else: + arr[i] = data[i] + data=arr + get_context().getDataManager().appendItem(path, data) + +def flush_data(): + """Flush all data files immediately. + + Args: + None + Returns: + None + """ + get_context().getDataManager().flush() + +def set_attribute(path, name, value, unsigned = False): + """Set an attribute to a group or dataset. + + Args: + path(str): Path to dataset relative to the current persistence context root. + name(str): name of the atttribute + value(Object): the attribute value + unsigned(bool, optional): if applies, indicate if value is unsigned. + Returns: + None + """ + if is_list(value): + value = Convert.toStringArray(to_array(value)) + get_context().getDataManager().setAttribute(path, name, value, unsigned) + +def log(log, data_file=None): + """Writes a log to the system log and data context - if there is an ongoing scan or script execution. + + Args: + log(str): Log string. + data_file(bool, optional): if true logs to the data file, in addiction to the system logger. + If None(default) appends to data file only if it exists. + + Returns: + None + """ + get_context().scriptingLog(str(log)) + if data_file is None: + data_file = get_exec_pars().open + if data_file: + try: + get_context().getDataManager().appendLog(str(log)) + except: + #Do not generate exception if cannot write to data file + pass + +def set_exec_pars(**args): + """ Configures the script execution parameters, overriding the system configuration. + + Args: + args(optional arguments): + name(str): value of the {name} tag. Default is the running script name. + type(str): value of the {type} tag. Default is empty. + This field can be used to store data in sub-folders of standard location. + path(str): If defined provides the full path name for data output root (overriding config)) + The tag {data} can be used to enter a path relative to the standard data folder. + layout(str): Change data layout. + format(str): Change data format. + split(scan or True): Split scan data to another table. If set to True in scan command then split every pass. + depth_dim(int): dimension of 2d-matrixes in 3d datasets. + save(bool): Change option to auto save scan data. + flush(bool): Change option to flush file on each record. + keep(bool): Change option keep scan records in memory. If false do not add records to scan result. + preserve(bool): Change option to preserve device types. If false all values are converted to double. + setpoints(bool): Save the positioner setpoints too. + verbose(bool): Enable options to save additional information (output, script). + compression(obj): True for enabling default compression, int for specifying deflation level. + Device or list of devices for specifying devices to be compressed. + shuffle(obj): True for enabling shuffling before compression. + Device or list of devices for specifying devices to be shuffled. + contiguous(obj): True for setting contiguous datasets for all devices. + Device or list of devices for specifying device datasets to be contiguous. + seq(int): Set next data file sequence number. + open(bool): If true create data output path immediately. If false closes output root, if open. + reset(bool): If true reset the scan counter - the {count} tag and set the timestamp to now. + group(str): Change layout group name for scans + tag(str): Change tag for scan names (affecting group or dataset name, according to layout) + then, then_success, then_exception(str): Sets statement to be executed on the completion of current. + defaults(bool): If true restore the original execution parameters. + + Graphical preferences: + line_plots(list): list of devices with enforced line plots. + range(str or list): "none", "auto", [min_x, max_x] or [min_x, max_x, min_y, max_y] + display(bool): if false disables scan data plotting and printing. + print_scan(bool): Enable/disables scan data printing to console. + plot_disabled(bool): Enable/disable scan plot + plot_layout (str):"Horizontal", "Vertical" or "Grid" + table_disabled(bool): Enable/disable scan table + enabled_plots (list of str or Readable): list of devices (Readables) to be plotted + plot_types(dict): Dictionary - Plot name(Readable or String) : Plot type(String or int) + auto_range(bool): If true automatic range scan plots x-axis. + manual_range(tuple): : Set range (min_x, max_x) or (min_x, max_x, min_y, max_y). None sets fixed range. + manual_range_y(tuple): Set y range (min_y, max_y). None sets fixed range. + domain_axis(str): Set the domain axis source: "Time", "Index", or a readable name. Default: first positioner. + status(str): set application status + """ + get_context().setExecutionPars(args) + +def get_exec_pars(): + """ Returns script execution parameters. + + Returns: + ExecutionParameters object. Fields: + name (str): execution name - {name} tag. + type (str): execution type - {type} tag. + path (str): output data root. + seq(int): data file sequence number. + open (bool): true if the output data root has been opened. + layout (str): data output layout. If None then using the configuration. + save (bool): auto save scan data option. + flush (bool): flush file on each record. + index (int): current scan index. + group (str): data group currently used for scan data storage. + if no ongoing scan return "/" if within a script, or else None if a console command. + scanPath (str): dataset or group corresponding to current scan. + scan (Scan): reference to current scan, if any + source (CommandSource): return the source of the script or command. + background (bool): return False if executing in main interpreter thread . + debug (bool): True if executing from statements in editor. + simulation (bool): global simulation flag. + aborted (bool): True if execution has been aborted + """ + return get_context().getExecutionPars() + + +################################################################################################### +#EPICS +################################################################################################### + +def _adjust_channel_value(value, var_type=None): + if (value is None): + return value + if (var_type is not None): + if is_list(value): + var_type = var_type.replace(',','').replace('[','') + ret = [] + for item in value: + ret.append(_adjust_channel_value(item), var_type) + value = ret + else: + var_type = var_type.lower() + if var_type=='b': + value = byte(value) + elif var_type=='i': + value = short(value) + elif var_type=='l': + value = int(value) + elif var_type=='f': + value = float(value) + elif var_type=='d': + value = float(value) + elif var_type=='s': + value = str(value) + + if isinstance(value,tuple): + value = list(value) + if isinstance(value,list): + list_type = type(value[0]) + array_types = { + int: "i", + long: "l", + float:"d", + str:Class.forName("java.lang.String"), + } + array_type = array_types.get(type(value[0]),'d') + array = PyArray(array_type) + array.fromlist(value) + value=array + return value + +def caget(name, type=None, size=None, meta = False ): + """Reads an Epics PV. + + Args: + name(str): PV name + type(str, optional): type of PV. By default gets the PV standard field type. + Scalar values: 'b', 'i', 'l', 'd', 's'. + Array values: '[b', '[i,', '[l', '[d', '[s'. + size (int, optional): for arrays, number of elements to be read. Default read all. + meta (bool, optional): if true gets channel value and metadata (timestamp, severity). + + Returns: + PV value if meta is false, otherwise a dictionary containing PV value and metadata + """ + if meta: + return Epics.getMeta(name, Epics.getChannelType(type), size) + return Epics.get(name, Epics.getChannelType(type), size) + +def cawait(name, value, timeout=None, comparator=None, type=None, size=None): + """Wait for a PV to have a given value. + + Args: + name(str): PV name + value (obj): value to compare to + timeout(float, optional): time in seconds to wait. If None, waits forever. + comparator(java.util.Comparator or float, optional): if None waits for equality. + If a numeric value is provided, waits for channel to be in range. + type(str, optional): type of PV. By default gets the PV standard field type. + Scalar values: 'b', 'i', 'l', 'd', 's'. + Array values: '[b', '[i,', '[l', '[d', '[s'. + size (int, optional): for arrays, number of elements to be read. Default read all. + + Returns: + None + """ + if (timeout is not None): + timeout = int(timeout*1000) + value = _adjust_channel_value(value) + Epics.waitValue(name, value, comparator, timeout, Epics.getChannelType(type), size) + +def caput(name, value, timeout = None): + """Writes to an Epics PV. + + Args: + name(str): PV name + value(scalar, string or array): new PV value. + timeout(int, optional): timeout in seconds to the write. If None waits forever to completion. + + Returns: + None + """ + value=_adjust_channel_value(value) + if (timeout is not None): + timeout = int(timeout*1000) + return Epics.put(name, value, timeout) + +def caputq(name, value): + """Writes to an Epics PV and does not wait. + + Args: + name(str): PV name + value(scalar, string or array): new PV value. + + Returns: + None + """ + value=_adjust_channel_value(value) + return Epics.putq(name, value) + +def camon(name, type=None, size=None, wait = sys.maxsize): + """Install a monitor to an Epics PV and print value changes. + + Args: + name(str): PV name + type(str, optional): type of PV. By default gets the PV standard field type. + Scalar values: 'b', 'i', 'l', 'd', 's'. + Array values: '[b', '[i,', '[l', '[d', '[s'. + size (int, optional): for arrays, number of elements to be read. Default read all. + wait (int, optional): blocking time for this function. By default blocks forever. + Returns: + None + """ + val = lambda x: x.tolist() if is_array(x) else x + + class MonitorListener(PropertyChangeListener): + def propertyChange(self, pce): + print (val(pce.getNewValue())) + + channel = create_channel(name, type, size) + print (val(channel.getValue())) + channel.setMonitored(True) + channel.addPropertyChangeListener(MonitorListener()) + + try: + time.sleep(wait) + finally: + Epics.closeChannel(channel) + +def create_channel_device(channel_name, type=None, size=None, device_name=None, monitored=False): + """Create a device from an EPICS PV. + + Args: + channel_name(str): PV name + type(str, optional): type of PV. By default gets the PV standard field type. + Scalar values: 'b', 'i', 'l', 'd', 's'. + Array values: '[b', '[i,', '[l', '[d', '[s'. + size (int, optional): for arrays, number of elements to be read. Default read all. + device_name (str, optional): device name (if different from hannel_name. + Returns: + None + """ + dev = Epics.newChannelDevice(channel_name if (device_name is None) else device_name , channel_name, Epics.getChannelType(type)) + if get_context().isSimulation(): + dev.setSimulated() + dev.initialize() + if (size is not None): + dev.setSize(size) + if (monitored): + dev.setMonitored(True) + return dev + + +################################################################################################### +#Concurrent execution +################################################################################################### +#TODO +""" +class Callable(concurrent.Callable): + def __init__(self, method, *args): + self.method = method + self.args = args + self.thread = Thread.currentThread() + def call(self): + try: + get_context().startedChildThread(self.thread) + return self.method(*self.args) + finally: + get_context().finishedChildThread(self.thread) +""" +def fork(*functions): + """Start execution of functions in parallel. + + Args: + *functions(function references) + + Returns: + List of callable + """ + callables = [] + for m in functions: + if is_list(m): + #callables.append(Callable(m[0],*m[1])) + callables.append(concurrent.Callable(m[0],*m[1])) + else: + #callables.append(Callable(m)) + callables.append(concurrent.Callable(m)) + return Threading.fork(callables) + +def join(futures): + """Wait parallel execution of functions. + + Args: + futures(Future or list of Future) : as returned from fork + + Returns: + None +""" + try: + futures=to_list(futures) + return Threading.join(futures) + except java.util.concurrent.ExecutionException as ex: + raise ex.getCause() + +def parallelize(*functions): + """Equivalent to fork + join + + Args: + *functions(function references) + + Returns: + None + """ + futures = fork(*functions) + return join(futures) + +def invoke(f, wait = False): + """ Execute in event thread. + + Args: + f(function reference) + wait (boolean, optional) + """ + if is_list(f): [m, a] = f; f = lambda: m(*a) + invokeAndWait(f) if wait else invokeLater(f) + + +################################################################################################### +#Background task control. +################################################################################################### + +def start_task(script, delay = 0.0, interval = -1): + """Start a background task + + Args: + script(str): Name of the script implementing the task + delay(float, optional): time in seconds for the first execution. + Default starts immediately. + interval(float, optional): time in seconds for between execution. + If negative (default), single-execution. + + Returns: + Task object. + """ + delay_ms=int(delay*1000) + interval_ms=int(interval*1000) if (interval>=0) else int(interval) + return get_context().startTask(script, delay_ms, interval_ms) + +def stop_task(script, force = False): + """Stop a background task + + Args: + script(str): Name of the script implementing the task + force(boolean, optional): interrupt current execution, if running + + Returns: + None + """ + get_context().stopTask(script, force) + + +################################################################################################### +#Versioning +################################################################################################### + +def commit(message, force = False): + """Commit the changes to the repository. + + Args: + message(str): commit message + force(bool, optional): if False, raises exception if no change detected in repo + + Returns: + None + """ + get_context().commit(message, force) + +def diff(): + """Return list of changes in the repository + + Args: + None + + Returns: + None + """ + return get_context().diff() + +def checkout_tag(tag): + """Checkout a tag name. + + Args: + tag(str): tag name. + + Returns: + None + """ + get_context().checkoutTag(tag) + +def checkout_branch(tag): + """Checkout a local branch name. + + Args: + tag(str): branch name. + + Returns: + None + """ + get_context().checkoutLocalBranch(tag) + +def pull_repository(): + """Pull from remote repository. + + """ + get_context().pullFromUpstream() + +def push_repository(all_branches=True, force=False, push_tags=False): + """Push to remote repository. + + Args: + all_branches(boolean, optional): all branches or just current. + force(boolean, optional): force flag. + push_tags(boolean, optional): push tags. + + Returns: + None + """ + get_context().pushToUpstream(all_branches, force, push_tags) + +def cleanup_repository(): + """Performs a repository cleanup. + + Args: + None + + Returns: + None + """ + get_context().cleanupRepository() + +################################################################################################### +#Device Pool +################################################################################################### + +def get_device(device_name): + """Returns a configured device (or imaging source) by its name. + + Args: + device_name(str): name of the device. + + Returns: + device + """ + return get_context().getDevicePool().getByName(device_name) + +def add_device(device, force = False): + """Add a device (or imaging source) to the device pool. + + Args: + device(Device or Source) + force(boolean, optional): if true then dispose existing device with same name. + Otherwise will fail in case of name clash. + + Returns: + True if device was added, false if was already in the pool, or exception in case of name clash. + """ + return get_context().getDevicePool().addDevice(device, force, True) + +def remove_device(device): + """Remove a device (or imaging source) from the device pool. + + Args: + device(Device or Source) + + Returns: + bool: true if device was removed. + """ + device=string_to_obj(device) + return get_context().getDevicePool().removeDevice(device) + +def set_device_alias(device, alias): + """Deprecated, use "dev.set_alias" instead. Set a device alias to be used in scans (datasets and plots). + + Args: + device(Device) + alias(str): replace device name in scans. + + Returns: + None + """ + device=string_to_obj(device) + device.setAlias(alias) + +def stop(): + """Stop all devices implementing the Stoppable interface. + + Args: + None + + Returns: + None + """ + get_context().stopAll() + +def update(): + """Update all devices. + + Args: + None + + Returns: + None + """ + get_context().updateAll() + +def reinit(dev = None): + """Re-initialize devices. + + Args: + dev(Device, optional): Device to be re-initialized (if None, all devices not yet initialized) + + Returns: + List with devices not initialized. + """ + if dev is not None: + dev=string_to_obj(dev) + return get_context().reinit(dev) + return to_list(get_context().reinit()) + +def create_device(url, parent=None): + """Create a device form a definition string(see InlineDevice) + + Args: + url(str or list of string): the device definition string (or list of strings) + parent(bool, optional): parent device + + Returns: + The created device (or list of devices) + """ + if parent is not None: + parent=string_to_obj(parent) + return InlineDevice.create(url, parent) + + +def create_averager(dev, count, interval=0.0, name = None, monitored = False): + """Creates and initializes and averager for dev. + + Args: + dev(Device): the source device + count(int): number of samples + interval(float, optional): sampling interval(s). If negative sampling is made on data change event. + name(str, optional): sets the name of the device (default is: averager) + monitored (bool, optional): if true then averager processes asynchronously. + + Returns: + Averager device + """ + dev = string_to_obj(dev) + if is_java_instance(dev, ReadableArray): + av = ArrayAverager(dev, count, int(interval*1000)) if (name is None) else ArrayAverager(name, dev, count, int(interval*1000)) + else: + av = Averager(dev, count, int(interval*1000)) if (name is None) else Averager(name, dev, count, int(interval*1000)) + av.initialize() + if (monitored): + av.monitored = True + return av + +def tweak(dev, step, is2d=False): + """Move one or more positioners in steps using the arrow keys. + Steps are increased/decreased using the shift and control keys. + + Args: + dev(Positioner or List): the device or list of devices to move. + step(float or List): step size or list of step sizes + is2d(bool, optional): if true moves second motor with up/down arrows. + """ + if (get_exec_pars().isBackground()): return + dev,step = to_list(string_to_obj(dev)),to_list(step) + while (True): + key=get_context().waitKey(0) + for i in range(len(dev)): + if not is2d or i==0: + if key == 0x25: dev[i].moveRel(-step[i]) #Left + elif key == 0x27: dev[i].moveRel(step[i]) #Right + if key in (0x10, 0x11): + step[i] = step[i]*2 if key == 0x10 else step[i]/2 + print ("Tweak step for " + dev[i].getName() + " set to: "+str(step[i])) + if is2d and len(dev)>1: + if key == 0x26: dev[1].moveRel(step[1]) #Top + elif key == 0x28: dev[1].moveRel(-step[1]) #Bottom + + +################################################################################################### +#Maths +################################################################################################### + +def arrmul(a, b): + """Multiply 2 series of the same size. + + Args: + + a(subscriptable) + b(subscriptable) + + Returns: + List + """ + return map(mul, a, b) + +def arrdiv(a, b): + """Divide 2 series of the same size. + + Args: + + a(subscriptable) + b(subscriptable) + + Returns: + List + """ + return map(truediv, a, b) + +def arradd(a, b): + """Add 2 series of the same size. + + Args: + + a(subscriptable) + b(subscriptable) + + Returns: + List + """ + return map(add, a, b) + +def arrsub(a, b): + """Subtract 2 series of the same size. + + Args: + + a(subscriptable) + b(subscriptable) + + Returns: + List + """ + return map(sub, a, b) + +def arrabs(a): + """Returns the absolute of all elements in series. + + Args: + + a(subscriptable) + + Returns: + List + """ + return map(abs, a) + +def arroff(a, value = "mean"): + """Subtract offset to all elemets in series. + + Args: + + a(subscriptable) + type(int or str, optional): value to subtract from the array, or "mean" or "min". + + Returns: + List + """ + if value=="mean": + value = mean(a) + elif value=="min": + value = min(a) + return [x-value for x in a] + +def mean(data): + """Calculate the mean of a sequence. + + Args: + data(subscriptable) + + Returns: + Mean of the elements in the object. + """ + return reduce(lambda x, y: x + y, data) / len(data) + +def variance(data): + """Calculate the variance of a sequence. + + Args: + data(subscriptable) + + Returns: + Variance of the elements in the object. + """ + c = mean(data) + ss = sum((x-c)**2 for x in data) + return ss/len(data) + +def stdev(data): + """Calculate the standard deviation of a sequence. + + Args: + data(subscriptable) + + Returns: + Standard deviation of the elements in the object. + """ + return variance(data)**0.5 + + +def center_of_mass(data, x = None): + """Calculate the center of mass of a series, and its rms. + + Args: + + data(subscriptable) + x(list, tuple, array ..., optional): x coordinates + + Returns: + Tuple (com, rms) + """ + if x is None: + x = Arr.indexesDouble(len(data)) + data_sum = sum(data) + if (data_sum==0): + return float('nan') + xmd = arrmul( x, data) + com = sum(xmd) / data_sum + xmd2 = arrmul( x, xmd) + com2 = sum(xmd2) / data_sum + rms = math.sqrt(abs(com2 - com * com)) + return (com, rms) + +def poly(val, coefs): + """Evaluates a polinomial: (coefs[0] + coefs[1]*val + coefs[2]*val^2... + + Args: + val(float): value + coefs (list of loats): polinomial coefficients + Returns: + Evaluated function for val + """ + r = 0 + p = 0 + for c in coefs: + r = r + c * math.pow(val, p) + p = p + 1 + return r + +def histogram(data, range_min = None, range_max = None, bin = 1.0): + """Creates histogram on data. + + Args: + data (tuple, array, ArrayList or Array): input data can be multi-dimensional or nested. + range_min (int, optional): minimum histogram value. Default is floor(min(data)) + range_max (int, optional): maximul histogram value. Default is ceil(max(data)) + bin(int or float, optional): if int means number of bins. If float means bin size. Default = 1.0. + Returns: + tuple: (ydata, xdata) + """ + if range_min is None: range_min = math.floor(min(flatten(data))) + if range_max is None: range_max = math.ceil(max(flatten(data))) + if type(bin) is float: + bin_size = bin + n_bin = int(math.ceil(float(range_max - range_min)/bin_size)) + else: + n_bin = bin + bin_size = float(range_max - range_min)/bin + + result = [0] * n_bin + for d in flatten(data): + b = int( float(d - range_min) / bin_size) + if (b >=0) and (b < n_bin): + result[b] = result[b] + 1 + return (result, frange(range_min, range_max, bin_size)) + +def _turn(p, q, r): + return cmp((q[0] - p[0])*(r[1] - p[1]) - (r[0] - p[0])*(q[1] - p[1]), 0) + +def _keep(hull, r): + while len(hull) > 1 and _turn(hull[-2], hull[-1], r) != 1: + hull.pop() + return (not len(hull) or hull[-1] != r) and hull.append(r) or hull + +def convex_hull(point_list=None, x=None, y=None): + """Returns the convex hull from a list of points. Either point_list or x,y is provided. + (Alhorithm taken from http://tomswitzer.net/2010/03/graham-scan/) + Args: + point_list (array of tuples, optional): arrays of the points + x (array of float, optional): array with x coords of points + y (array of float, optional): array with y coords of points + Returns: + Array of points or (x,y) + """ + is_point_list = point_list is not None + if not point_list: + point_list=[] + for i in range(len(x)): + if((x[i] is not None) and (y[i] is not None)): point_list.append((x[i], y[i])) + point_list.sort() + lh,uh = reduce(_keep, point_list, []), reduce(_keep, reversed(point_list), []) + ret = lh.extend(uh[i] for i in xrange(1, len(uh) - 1)) or lh + if not is_point_list: + x, y = [], [] + for i in range(len(ret)): + x.append(ret[i][0]) + y.append(ret[i][1]) + return (x,y) + return ret + +################################################################################################### +#Utilities +################################################################################################### + +def get_setting(name=None): + """Get a persisted script setting value. + + Args: + name (str): name of the setting. + Returns: + String with setting value or None if setting is undefined. + If name is None then returns map with all settings. + """ + return get_context().getSettings() if (name is None) else get_context().getSetting(name) + +def set_setting(name, value): + """Set a persisted script setting value. + + Args: + name (str): name of the setting. + value (obj): value for the setting, converted to string (if None then remove the setting). + Returns: + None. + """ + get_context().setSetting(name, value) + +def exec_cmd(cmd, stderr_raise_ex = True): + """Executes a shell command. If errors happens raises an exception. + + Args: + cmd (str): command process input. If stderr_raise_ex is set then raise exception if stderr is not null. + Returns: + Output of command process. + """ + import subprocess + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE if stderr_raise_ex else subprocess.STDOUT, shell=True) + (ret, err) = proc.communicate() + if stderr_raise_ex and (err is not None) and err!="": + raise Exception(err) + return ret + +def bsget(channel, modulo=1, offset=0, timeout = 5.0): + """Reads an values a bsread stream, using the default provider. + + Args: + channel(str or list of str): channel name(s) + module(int, optional): stream modulo + offset(int, optional): stream offset + timeout(float, optional): stream timeout in secs + Returns: + BS value or list of values + """ + channels = to_list(channel) + ret = Stream.readChannels(channels, modulo, offset, int(timeout * 1000)) + if is_string(channel): + return ret[0] + return ret + +def flatten(data): + """Flattens multi-dimentional or nested data. + + Args: + data (tuple, array, ArrayList or Array): input data + Returns: + Iterator on the flattened data. + """ + if is_array(data): + if not data.typecode.startswith('['): + return data + + import itertools + return itertools.chain(*data) + +def frange_gen(start, finish, step): + while ((step >= 0.0) and (start <= finish)) or ((step < 0.0) and (start >= finish)): + yield start + start += step + +def frange(start, finish, step, enforce_finish = False, inclusive_finish = False): + """Create a list with a range of float values (a float equivalent to "range"). + + Args: + start(float): start of range. + finish(float): end of range. + step(float): step size. + enforce_finish(boolean, optional): adds the final element even if range was not exact. + inclusive_finish(boolean, optional): if false finish is exclusive (like in "range"). + + Returns: + list + """ + step = float(step) + ret = list(frange_gen(start, finish, step)) + if len(ret) > 0: + if inclusive_finish == False: + if ret[-1]==finish: + del ret[-1] + if enforce_finish and ret[-1]!=finish: + ret.append(finish) + return ret + +def notify(subject, text, attachments = None, to=None): + """Send email message. + + Args: + subject(str): Message subject. + text(str): Message body. + attachments(list of str, optional): list of files to be attached (expansion tokens are allowed). + to (list ofd str, optional): recipients. If None uses the recipients defined in mail.properties. + Returns: + None + """ + get_context().notify(subject, text, to_list(attachments), to_list(to)) + +def expand_path(path, timestamp=-1): + """Expand path containing tokens. + + Args: + path(str): path name. + timestamp(int): If not defined(-1), uses now. + Returns: + Expanded path name. + """ + + return get_context().getSetup().expandPath(path, timestamp) + +################################################################################################### +#UI +################################################################################################### + +def set_status(status): + """Set the application status. + + Args: + status(str): new status. + + Returns: + None + """ + set_preference(Preference.STATUS, status) + +def setup_plotting( enable_plots=None, enable_table=None,plot_list=None, line_plots=None, range=None, domain=None, defaults=None): + if defaults == True: set_preference(Preference.DEFAULTS, True) + if enable_plots is not None: set_preference(Preference.PLOT_DISABLED, not enable_plots) + if enable_table is not None: set_preference(Preference.TABLE_DISABLED, not enable_table) + if plot_list is not None: set_preference(Preference.ENABLED_PLOTS, None if plot_list == "all" else plot_list) + if line_plots is not None: + plots = None + if line_plots != "none": + plots = {} + for p in line_plots: plots[p]=1 + set_preference(Preference.PLOT_TYPES, plots) + if range is not None: + if range == "none": set_preference(Preference.AUTO_RANGE, None) + elif range == "auto": set_preference(Preference.AUTO_RANGE, True) + else: set_preference(Preference.MANUAL_RANGE, range) + if domain is not None: set_preference(Preference.DOMAIN_AXIS, domain) + +def set_preference(preference, value): + """Hints to graphical layer: + + Args: + preference(Preference): Enum of preference types: + PLOT_DISABLED: enable/disable scan plot (True/False) + PLOT_LAYOUT: "Horizontal", "Vertical" or "Grid" + TABLE_DISABLED: enable/disable scan table (True/False) + ENABLED_PLOTS: select Readables to be plotted (list of Readable or String (names)) + PLOT_TYPES: Dictionary - Plot name(Readable or String) : Plot type(String or int) + PRINT_SCAN: Print scan records to console + AUTO_RANGE: Automatic range scan plots x-axis + MANUAL_RANGE: Manually set scan plots x-axis + MANUAL_RANGE_Y: Manually set scan plots y-axis + DOMAIN_AXIS: Set the domain axis source: "Time", "Index", or a readable name. + Default(None): first positioner + STATUS: set application status + value(object): preference value + + Returns: + None + """ + value = to_array(value, 'o') #If list then convert to Object array + get_context().setPreference(preference, value) + +def get_string(msg, default = None, alternatives = None, password = False): + """ + Reads a string from UI + Args: + msg(str): display message. + default(str, optional): value displayed when window is shown. + alternatives(list of str, optional): if provided presents a combo box instead of an editing field. + password(boolean, optional): if True hides entered characters. + + Returns: + String entered of null if canceled + """ + if password : + return get_context().getPassword(msg, None) + return get_context().getString(msg, str(default) if (default is not None) else None, alternatives) + +def get_option(msg, type = "YesNoCancel"): + """ + Gets an option from UI + Args: + msg(str): display message. + type(str, optional): 'YesNo','YesNoCancel' or 'OkCancel' + + Returns: + 'Yes', 'No', 'Cancel' + """ + return get_context().getOption(msg, type) + +def show_message(msg, title=None, blocking = True): + """ + Pops a blocking message to UI + + Args: + msg(str): display message. + title(str, optional): dialog title + """ + get_context().showMessage(msg, title, blocking) + +def show_panel(device, title=None): + """ + Show, if exists, the panel relative to this device. + + Args: + device(Device or str or BufferedImage): device + title only apply to BufferedImage objects. For devices the title is the device name. + """ + if type(device) is BufferedImage: + device = DirectSource(title, device) + device.initialize() + if is_string(device): + device = get_device(device) + return get_context().showPanel(device) + + + +################################################################################################### +#Executed on startup +################################################################################################### + +if __name__ == "___main__": + ca_channel_path=os.path.join(get_context().setup.getStandardLibraryPath(), "epics") + sys.path.append(ca_channel_path) + #This is to destroy previous context of _ca (it is not shared with PShell) + if run_count > 0: + if sys.modules.has_key("_ca"): + print("") + import _ca + _ca.initialize() diff --git a/script/cpy/testJepScan.py b/script/cpy/testJepScan.py new file mode 100644 index 0000000..73bc2a8 --- /dev/null +++ b/script/cpy/testJepScan.py @@ -0,0 +1,30 @@ + + +#r = tscan((sin,out), 100, 0.01) +#vector = [ 1, 3, 5, 10, 25, 40, 45, 47, 49] +#r = vscan(pv,(sin,out),vector,False, 0.5, title = "1D Vector") +#r = mscan(sin,(sin,out),-1, 3,0) +#r = rscan(pv,(sin,out) , [(0.0,5.0,1.0), (10.0,15.0,0.1), (20.0,25.0,1.0)] , 0.01) +#r = cscan(motor, (sin,out), 0.0, 2.0 , steps=10.0, time=4.) + +print (get_exec_pars().getPath()) + +def before_pass(pass_num): + print ("Starting pass: " , pass_num) +def after_pass(pass_num): + print ("Finished pass: " , pass_num) + +#set_exec_pars(layout="sf") + +ret= lscan(inp, (sin,out), 0, 40, 50, 0.2, passes = 1, before_pass = before_pass, after_pass=after_pass,title = "Test") +#ret= lscan(inp, (sin,out), 0, 40, 50, 0.2) + +#set_exec_pars(then="run('test/test3.py)") +#set_return([1.0, 3, [1,2,3,4]]) +#1/0 +#set_return("T\"es\"t") +#set_return([3.0,2]) +set_return(ret) + + + \ No newline at end of file diff --git a/script/cpy/xxx.py b/script/cpy/xxx.py new file mode 100644 index 0000000..4997861 --- /dev/null +++ b/script/cpy/xxx.py @@ -0,0 +1,115 @@ +################################################################################################### +# This moddule is called by demo scripts to execute and embed CPython. +# Must be put in the scripts folder, or else in the python path. +################################################################################################### +import matplotlib +""" +['GTK3Agg', 'GTK3Cairo', 'MacOSX', 'nbAgg', 'Qt4Agg', 'Qt4Cairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'TkCairo', +'WebAgg', 'WX', 'WXAgg', 'WXCairo', 'agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template'] +""" +matplotlib.use('Qt5Agg') + +import sys +import os + +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +try: + import tkinter as tk +except: + import Tkinter as tk + + +def calc(array): + return np.transpose(array + array) + + +def test_pandas(): + s = pd.Series([1,3,5,np.nan,6,8]) + print (s) + dates = pd.date_range('20130101', periods=6) + print (dates) + df = pd.DataFrame(np.random.randn(6,4), index=dates, columns=list('ABCD')) + print (df) + df2 = pd.DataFrame({ 'A' : 1., + 'B' : pd.Timestamp('20130102'), + 'C' : pd.Series(1,index=list(range(4)),dtype='float32'), + 'D' : np.array([3] * 4,dtype='int32'), + 'E' : pd.Categorical(["test","train","test","train"]), + 'F' : 'foo' }) + print (df2) + print (df2.dtypes) + print (df.head()) + print (df.tail(3)) + print (df.values) + print (df.describe()) + print (df.T) + print (df.sort_index(axis=1, ascending=False)) + #print (df.sort_values(by='B')) + print (df['A']) + print (df[0:3]) + print (df.mean()) + return str(df.mean()) + + +def test_tkinter(): + root = tk.Tk() + listb = tk.Listbox(root) + for item in ["Hello", "World"]: + listb.insert(0,item) + listb.pack() + root.mainloop() + +print ("OK") + +def test_matplotlib(start,stop,step): + import threading + x = np.arange(start,stop,step) + y = np.exp(-x) + + # example variable error bar values + yerr = 0.1 + 0.2*np.sqrt(x) + xerr = 0.1 + yerr + + # First illustrate basic pyplot interface, using defaults where possible. + plt.figure() + plt.errorbar(x, y, xerr=0.2, yerr=0.4) + plt.title("Simplest errorbars, 0.2 in x, 0.4 in y") + + # Now switch to a more OO interface to exercise more features. + fig, axs = plt.subplots(nrows=2, ncols=2, sharex=True) + ax = axs[0,0] + ax.errorbar(x, y, yerr=yerr, fmt='o') + ax.set_title('Vert. symmetric') + + # With 4 subplots, reduce the number of axis ticks to avoid crowding. + ax.locator_params(nbins=4) + + ax = axs[0,1] + ax.errorbar(x, y, xerr=xerr, fmt='o') + ax.set_title('Hor. symmetric') + + ax = axs[1,0] + ax.errorbar(x, y, yerr=[yerr, 2*yerr], xerr=[xerr, 2*xerr], fmt='--o') + ax.set_title('H, V asymmetric') + + ax = axs[1,1] + ax.set_yscale('log') + # Here we have to be careful to keep all y values positive: + ylower = np.maximum(1e-2, y - yerr) + yerr_lower = y - ylower + + ax.errorbar(x, y, yerr=[yerr_lower, 2*yerr], xerr=xerr, + fmt='o', ecolor='g', capthick=2) + ax.set_title('Mixed sym., log y') + + fig.suptitle('Variable errorbars') + + plt.show() + return [start,stop,step] + + +test_pandas() +test_matplotlib(0,100,100) \ No newline at end of file diff --git a/script/cpython/gfitoff.py b/script/cpython/gfitoff.py new file mode 100644 index 0000000..31a1e1b --- /dev/null +++ b/script/cpython/gfitoff.py @@ -0,0 +1,30 @@ +import numpy as np +import scipy.optimize + + +def gfitoff(x, y, off=None, amp=None, com=None, sigma=None): + if off is None: + off = y.min() # good enough starting point for offset + + if com is None: + com = x[y.argmax()] + + if amp is None: + amp = y.max() - off + + # For normalised gauss curve sigma=1/(amp*sqrt(2*pi)) + if sigma is None: + surface = np.trapz((y-off), x=x) + sigma = surface / (amp * np.sqrt(2 * np.pi)) + try: + popt, pcov = scipy.optimize.curve_fit(gauss_fn, x, y, p0=[off, amp, com, sigma], method='lm') + popt[3] = abs(popt[3]) # sigma should be returned as positive + except Exception as e: + print("Gauss fitting not successful.\n" + str(e)) + popt = [off, amp, com, abs(sigma)] + + return popt + + +def gauss_fn(x, a, b, c, d): + return a + b * np.exp(-(np.power((x - c), 2) / (2 * np.power(d, 2)))) \ No newline at end of file diff --git a/script/cpython/linfit.py b/script/cpython/linfit.py new file mode 100644 index 0000000..01644b1 --- /dev/null +++ b/script/cpython/linfit.py @@ -0,0 +1,32 @@ +import numpy as np + +def linfit(x, y): + """ + Return linear fit + """ + p = np.polyfit(x, y, 1) + f = np.poly1d(p) + x_fit = np.linspace(min(x), max(x), 100) + y_fit = f(x_fit) + yhat = f(x) + ybar = np.sum(y)/len(y) + ssreg = np.sum((yhat - ybar)**2) + sstot = np.sum((y - ybar)**2) + R2 = ssreg / sstot + return (p, x_fit, y_fit, R2) + +def test(): + return np.ones(5) + + +def test2(name, x=None, y=None): + print (name,x,y) + ret = y*x + print (ret) + return ret + +def add(x,y,z): + return x+y+z + +def read_dev(dev): + return dev.read() \ No newline at end of file diff --git a/script/cpython/test.py b/script/cpython/test.py new file mode 100644 index 0000000..dde8d55 --- /dev/null +++ b/script/cpython/test.py @@ -0,0 +1,34 @@ +from jeputils import import_py + +import_py("cpython/linfit", "linfit") +import_py("cpython/gfitoff", "gfitoff") +import_py("CPython/linfit", "add") +import_py("CPython/linfit", "test") +import_py("CPython/linfit", "test2") +import_py("CPython/linfit", "read_dev") + +print read_dev(sin) + + +a,b,c =to_array([0,1,2,3,4],'d'), to_array([5,6,7,8,9],'d'), to_array([10, 11, 12, 13, 14],'d') +print test() +print test2("x", x=a,y=b) +print add(a,b,c) + +x=[0,1,2,3,4,5,6,7,8,9] +y=[1,2,3,6,9,6,3,2,1,0] +(p, x_fit, y_fit, R2) = linfit(x,y) +#print "Fit: ", (p, x_fit, y_fit, R2) +plot((y,y_fit), name=("data", "fit"),xdata=(x,x_fit)) + +time.sleep(2.0) +from mathutils import Gaussian +x=to_array([-200.30429237268825, -200.2650700434188, -200.22115208318002, -199.9457671375377, -199.86345548879072, -199.85213073174933, -199.35687977133284, -199.13811861090275, -197.97304970346386, -197.2952215624348, -195.09076092936948, -192.92276048970703, -191.96871876227698, -189.49577852322938, -187.9652790409825, -183.63756456925222, -180.04899765472996, -178.43839623242422, -174.07311671294445, -172.0410133577918, -165.90824309893102, -160.99771795989466, -159.30176653939253, -154.27688897558514, -152.0854103810786, -145.75652847587313, -140.80843828908465, -139.23982133191495, -134.27073891256106, -132.12649284133064, -125.95947209775511, -121.00309550337462, -119.26736932643232, -114.2706655484383, -112.07393889578914, -105.72295990367157, -100.8088439880125, -99.2034906238494, -94.30042325164636, -92.15010048151461, -85.92203653534293, -81.03913275494665, -79.27412793784428, -74.33487658582118, -72.06274362408762, -65.76562628131825, -60.91255356825276, -59.20334389560392, -54.33286972659312, -52.19387171350535, -45.94978737932291, -41.03014719193582, -39.301602568238906, -34.35572209014114, -32.04464301272608, -25.8221033382824, -20.922074315528747, -19.21590299233186, -14.31090212502093, -12.217203140101386, -5.9283722049240435, -0.9863587170369246, 0.7408048387279834, 5.71126832601389, 7.972628957879352, 14.204559894256546, 19.11839959633025, 20.8218087836657, 25.678748486941828, 27.822718344586864, 34.062659474970715, 38.9745656819391, 40.77409719734158, 45.72080631619803, 47.974156754056835, 54.23453768983539, 59.12020360609568, 60.77306570712026, 65.70734521458867, 67.8344660434617, 74.03187028154134, 78.96532114824849, 80.76070945985495, 85.74802197591286, 87.9140889204674, 94.18082276873524, 99.25790470037091, 100.68454787413205, 105.7213026221542, 107.79483801526698, 113.99555681638138, 119.0707052529143, 120.72715813056156, 125.77551384921307, 127.91257836719551, 134.2011330887875, 139.23043006997628, 140.71673537840158, 145.76288138835983, 147.80216629676042, 154.06420451405637, 159.0846626604798, 160.76183155710717, 165.73699067536242, 167.9265357747636, 173.96705069576544, 178.2522282751915, 179.9042617354548, 183.54586165856657, 185.23269803071796, 189.41678143751972, 191.87149157986588, 192.8741468985015, 195.0241934550453, 195.966634211846, 197.9821647518146, 198.99006812859284, 199.33202054855676, 199.91897441965887, 200.11536227958896, 200.22280936469997, 200.25181179127208],'d') +y=to_array([11.0, 6.0, 8.0, 5.0, 11.0, 7.0, 18.0, 11.0, 12.0, 10.0, 8.0, 6.0, 16.0, 4.0, 12.0, 9.0, 15.0, 14.0, 8.0, 20.0, 15.0, 8.0, 9.0, 11.0, 13.0, 12.0, 13.0, 15.0, 13.0, 20.0, 10.0, 7.0, 17.0, 11.0, 20.0, 13.0, 13.0, 23.0, 14.0, 10.0, 17.0, 15.0, 20.0, 16.0, 14.0, 13.0, 18.0, 22.0, 9.0, 20.0, 12.0, 14.0, 17.0, 19.0, 14.0, 14.0, 23.0, 19.0, 15.0, 20.0, 20.0, 21.0, 20.0, 23.0, 22.0, 15.0, 10.0, 17.0, 21.0, 15.0, 23.0, 23.0, 25.0, 18.0, 16.0, 21.0, 22.0, 16.0, 16.0, 14.0, 19.0, 20.0, 18.0, 20.0, 23.0, 13.0, 16.0, 20.0, 25.0, 15.0, 15.0, 17.0, 22.0, 26.0, 19.0, 30.0, 25.0, 17.0, 17.0, 23.0, 16.0, 27.0, 21.0, 21.0, 26.0, 27.0, 21.0, 17.0, 20.0, 20.0, 21.0, 19.0, 25.0, 19.0, 13.0, 23.0, 20.0, 20.0, 18.0, 20.0, 19.0, 25.0],'d') +[off, amp, com, sigma] = gfitoff(x, y, off=None, amp=None, com=None, sigma=None) +#print "Fit: ", [off, amp, com, sigma] +g = Gaussian(amp, com, sigma) +plot([y, [g.value(i)+off for i in x]], ["data", "fit"], xdata = x) + + + diff --git a/script/cpython/test2.py b/script/cpython/test2.py new file mode 100644 index 0000000..b65d4c7 --- /dev/null +++ b/script/cpython/test2.py @@ -0,0 +1,14 @@ +from jeputils import import_py + +import_py("cpython/gfitoff", "gfitoff") + +from mathutils import Gaussian +x=to_array([-200.30429237268825, -200.2650700434188, -200.22115208318002, -199.9457671375377, -199.86345548879072, -199.85213073174933, -199.35687977133284, -199.13811861090275, -197.97304970346386, -197.2952215624348, -195.09076092936948, -192.92276048970703, -191.96871876227698, -189.49577852322938, -187.9652790409825, -183.63756456925222, -180.04899765472996, -178.43839623242422, -174.07311671294445, -172.0410133577918, -165.90824309893102, -160.99771795989466, -159.30176653939253, -154.27688897558514, -152.0854103810786, -145.75652847587313, -140.80843828908465, -139.23982133191495, -134.27073891256106, -132.12649284133064, -125.95947209775511, -121.00309550337462, -119.26736932643232, -114.2706655484383, -112.07393889578914, -105.72295990367157, -100.8088439880125, -99.2034906238494, -94.30042325164636, -92.15010048151461, -85.92203653534293, -81.03913275494665, -79.27412793784428, -74.33487658582118, -72.06274362408762, -65.76562628131825, -60.91255356825276, -59.20334389560392, -54.33286972659312, -52.19387171350535, -45.94978737932291, -41.03014719193582, -39.301602568238906, -34.35572209014114, -32.04464301272608, -25.8221033382824, -20.922074315528747, -19.21590299233186, -14.31090212502093, -12.217203140101386, -5.9283722049240435, -0.9863587170369246, 0.7408048387279834, 5.71126832601389, 7.972628957879352, 14.204559894256546, 19.11839959633025, 20.8218087836657, 25.678748486941828, 27.822718344586864, 34.062659474970715, 38.9745656819391, 40.77409719734158, 45.72080631619803, 47.974156754056835, 54.23453768983539, 59.12020360609568, 60.77306570712026, 65.70734521458867, 67.8344660434617, 74.03187028154134, 78.96532114824849, 80.76070945985495, 85.74802197591286, 87.9140889204674, 94.18082276873524, 99.25790470037091, 100.68454787413205, 105.7213026221542, 107.79483801526698, 113.99555681638138, 119.0707052529143, 120.72715813056156, 125.77551384921307, 127.91257836719551, 134.2011330887875, 139.23043006997628, 140.71673537840158, 145.76288138835983, 147.80216629676042, 154.06420451405637, 159.0846626604798, 160.76183155710717, 165.73699067536242, 167.9265357747636, 173.96705069576544, 178.2522282751915, 179.9042617354548, 183.54586165856657, 185.23269803071796, 189.41678143751972, 191.87149157986588, 192.8741468985015, 195.0241934550453, 195.966634211846, 197.9821647518146, 198.99006812859284, 199.33202054855676, 199.91897441965887, 200.11536227958896, 200.22280936469997, 200.25181179127208],'d') +y=to_array([11.0, 6.0, 8.0, 5.0, 11.0, 7.0, 18.0, 11.0, 12.0, 10.0, 8.0, 6.0, 16.0, 4.0, 12.0, 9.0, 15.0, 14.0, 8.0, 20.0, 15.0, 8.0, 9.0, 11.0, 13.0, 12.0, 13.0, 15.0, 13.0, 20.0, 10.0, 7.0, 17.0, 11.0, 20.0, 13.0, 13.0, 23.0, 14.0, 10.0, 17.0, 15.0, 20.0, 16.0, 14.0, 13.0, 18.0, 22.0, 9.0, 20.0, 12.0, 14.0, 17.0, 19.0, 14.0, 14.0, 23.0, 19.0, 15.0, 20.0, 20.0, 21.0, 20.0, 23.0, 22.0, 15.0, 10.0, 17.0, 21.0, 15.0, 23.0, 23.0, 25.0, 18.0, 16.0, 21.0, 22.0, 16.0, 16.0, 14.0, 19.0, 20.0, 18.0, 20.0, 23.0, 13.0, 16.0, 20.0, 25.0, 15.0, 15.0, 17.0, 22.0, 26.0, 19.0, 30.0, 25.0, 17.0, 17.0, 23.0, 16.0, 27.0, 21.0, 21.0, 26.0, 27.0, 21.0, 17.0, 20.0, 20.0, 21.0, 19.0, 25.0, 19.0, 13.0, 23.0, 20.0, 20.0, 18.0, 20.0, 19.0, 25.0],'d') +[off, amp, com, sigma] = gfitoff(x, y, off=None, amp=None, com=None, sigma=None) +print "Fit: ", [off, amp, com, sigma] +g = Gaussian(amp, com, sigma) +plot([y, [g.value(i)+off for i in x]], ["data", "fit"], xdata = x) + + + diff --git a/script/diffcalc_test/.DS_Store b/script/diffcalc_test/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..55cae5018e2fc473b882d36f5f18709b92cfb7d9 GIT binary patch literal 6148 zcmeHKJxc>Y5S@uB0pqL=nCoLBO5&OTDiwsTQJx141bArAM@US;58WYsm#;21KxNy z-Uj2ne8W0BV4)Zm3WNfoKqwFj>?wdVn>HF7Mji@;0-?Z80qze8iC8&yhI#8imY)DX zUZbDQQGrHPe`2W7QJ*}na_kI^F6xgD^^w0GFPcY3|0#!yR)&#> z0--=(ffFlEc>Z7GmnlB-`;;h!0-?Y^Q$UCF+xZNO^0W15c|20: + roi.threshold(outliers_threshold, False, 0.0) + + return roi.integrate(False) + + +class RoiIntensitySourceListener (ImageListener): + def __init__(self, parent): + self.parent = parent + def onImage(self, origin, image, data): + self.parent.update() + def onError(self, origin, ex): + pass + + +class RoiIntensity(ReadonlyRegisterBase): + def __init__(self, name, source, x,y, w, h): + ReadonlyRegisterBase.__init__(self, name) + self.source=source + self.roi = x,y, w, h + self.source_listener = RoiIntensitySourceListener(self) + + def doRead(self): + x,y, w, h = self.roi + + ret= integrate_roi(self.source, x,y, w, h) + print "Read " + self.name + " -> " + str(ret) + return ret + + def doSetMonitored(self, value): + if value: + self.source.addListener(self.source_listener) + else: + self.source.removeListener(self.source_listener) + + def doClose(self): + self.source.removeListener(self.source_listener) + +def create_roi_devices(roi_list, add = True): + rois = [] + for r in roi_list: + roi = RoiIntensity(r, image, roi_list[r][0], roi_list[r][1], roi_list[r][2], roi_list[r][3]) + if add: + add_device(roi, True) + rois.append(roi) + return rois + +############################################################################### +# Frame integration +############################################################################### +#chrono_grab= Chrono() +def grab_frame(source, roi=None, wait_next=False, outliers_threshold=None, outliers_mask=None, retries=None, timeout=None): + global eiger_averaging_number_of_samples #, chrono_grab + + #chrono_grab.waitTimeout(GRAB_MIN_TIME) + if outliers_threshold is None: + outliers_threshold = get_outliers_threshold() + if outliers_mask is None: + outliers_mask = get_outliers_mask() + if wait_next: + if retries is None: + retries = 3 + if timeout is None: + timeout=10.0 + exposures = 1 if (eiger_averaging_number_of_samples is None) else eiger_averaging_number_of_samples + retries=max(retries,1) + timeout = 5000 + for try_count in range(retries): + try: + start =time.time() + id = image.take() + print "Waiting next " + source.waitNext(timeout) + #det.update() + #time.sleep(1) + print "Waiting done ", str(time.time()-start), " ", str(id), "->", str(image.take()) + break + except java.util.concurrent.TimeoutException: + if try_count == (retries-1): + raise + msg = "Eiger timeout - retrying #" + str(try_count) + print msg + log(msg) + + #ret = load_image(Utils.grayscale(source.output, Rectangle(roi[0], roi[1], roi[2], roi[3]) if (roi is not None) else None)) + time.sleep(0.01) + data=source.data + if roi is not None: + data = data.getRoi(Rectangle(roi[0], roi[1], roi[2], roi[3])) + #ret = load_image(img) + if outliers_mask is not None: + data.mult(outliers_mask) + if outliers_threshold>0: + data.threshold(outliers_threshold, False, None) + #chrono_grab = Chrono() + return data + +def grab_frames(source, samples, roi=None, wait_next=False, sleep=0, outliers_threshold=None, outliers_mask=None, retries=None, timeout=None): + frames = [] + for i in range(samples): + if (i>0) and (sleep>0): + time.sleep(sleep) + aux = grab_frame(source, roi, wait_next, outliers_threshold, outliers_mask, retries, timeout) + frames.append(aux) + return frames + +def integrate_frames(frames): + if frames is None or (len(frames)==0): + return None + ret = frames[0].copy() + for data in frames[1:]: + ret.sum(data) + return ret + +f,f1,f2=None, None, None +def average_frames(frames): + global f,f1,f2 + f1,f2 = frames + ret = integrate_frames(frames) + if ret is not None: + ret.div(len(frames)) + f=ret + return ret + + +def _timestamp(prec=0): + t = time.time() + s = time.strftime("%y/%m/%d %H:%M:%S", time.localtime(t)) + if prec > 0: + s += ("%.9f" % (t % 1,))[1:2+prec] + return s + +def _save_as_tiff(data, filename, check=False, show = False, metadata={}): + if type(data) == Data: + ip = load_array(data.matrix) + else: + ip = data + #info = "Timestamp: " + _timestamp(3) + #for key,val in metadata.items(): + # info = info + "\n" + str(key) + ": " + str(val) + #print "Info:" ,info + #ip.setProperty("Info", info) + metadata["Timestamp"] = time.strftime("%y/%m/%d %H:%M:%S",time.localtime()) + if not os.path.exists(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + save_image(ip, filename,"tiff", metadata) + + #finfo = open(filename + ".info", "w") + #for k, v in metadata.items(): + # finfo.write(str(k) + ': '+ str(v) + '\n') + #info.close() + + if check: + data = get_ip_array(ip) + + ip=open_image(filename) + read = get_ip_array(ip) + if not Arrays.deepEquals(read, data): + print "Error checking array" + +def save_as_tiff(data, filename, check=False, show = False, parallel=True, metadata={}): + if parallel: + return fork((_save_as_tiff,(data, filename, check, show, metadata)),) + else: + _save_as_tiff(data, filename, check, show, metadata) + +def trigger_eiger(wait=True): + if wait: + image.waitNext(20000) + +def get_eiger_exposure_readback(): + return 1.0 + +def set_exposure_time(value, check = True, retries=5): + pass + +def get_eiger_number_of_frames(): + return 1 + +def set_eiger_number_of_frames(value, check = True): + pass + + #Wait for channel to chenge + +def stop_eiger(): + pass + +chrono_eiger = Chrono() + +def init_eiger(exposure=None, check=True, retries=2): + """ + Set Eiger scan mode + """ + pass + +def restore_eiger(check=True, retries=2, exposure_time = 0.2): + """ + Set Eiger default mode + """ + pass + +def is_averaging_detector(): + return False + +eiger_averaging_number_of_samples=None + +def apply_averaging_detector(value): + pass + + +def average_eiger_frames(samples, roi=None, wait_next=False, sleep=0, outliers_threshold=None, outliers_mask=None, retries=None, timeout=None): + global eiger_averaging_number_of_samples #, chrono_eiger + sample = int(samples) + ret = grab_frames(image, samples, roi, wait_next, sleep, outliers_threshold, outliers_mask, retries, timeout) + #print "Averaging frames " + str(len(ret)) + " " + str(ret[0].integrate(False)) + " " + str(ret[1].integrate(False)) + " " + str(ret[0].equals(ret[1])) + print "Averaging frames " + + av = average_frames(ret) if samples > 1 else ret[0] + """ + for name,r in ROI.items(): + s, s1, s2 =0.0, 0.0, 0.0 + for i in range(r[2]): + for j in range(r[3]): + #s = s + d[r[0]+i][r[1]+j] + s = s + av.getElement(r[0]+i, r[1]+j, False) + s1 = s1 + ret[0].getElement(r[0]+i, r[1]+j, False) + s2 = s2 + ret[1].getElement(r[0]+i, r[1]+j, False) + + print "- ", name, s, s1,s2 + """ + return av + + +_outliers_mask_timestamp = 0 +_outliers_mask = None + +def get_outliers_mask(data_type='f'): + global _outliers_mask_timestamp, _outliers_mask + + if get_exec_pars().start == _outliers_mask_timestamp: + return _outliers_mask + _outliers_mask_timestamp = get_exec_pars().start + try: + _outliers_mask = None + filename = get_outliers_mask_file() + if filename: + ip=open_image(filename) + + #TRANSPOSE - ImageJ stores the data transposed + ip.getProcessor().rotate(-90) + ip.getProcessor().flipVertical() + + array = get_ip_array(ip) + array = Convert.toPrimitiveArray(array, ScriptUtils.getType(data_type)) + _outliers_mask = Data(array) + print "Generated outliers mask" + except: + pass + return _outliers_mask + + + + +if False: + integrate_roi(image, 10, 5, 20, 10) + + add_device(RoiIntensity("Region1", image, 10, 5, 20, 10), True) + add_device(RoiIntensity("Region2", image, 10, 5, 40, 20), True) + + + import ch.psi.pshell.data.ProviderCSV as ProviderCSV + ProviderCSV.setDefaultItemSeparator(" ") + tscan((Region1, Region2), 10, 0.1, layout="table", provider = "csv") + + ret = grab_frames(image, 10, sleep=0.1) + av = integrate_frames(ret) + save_as_tiff(av,"{images}/data.tif", True) + + + print "Success" + + + + diff --git a/script/imaging/sim.py b/script/imaging/sim.py new file mode 100644 index 0000000..fa56b15 --- /dev/null +++ b/script/imaging/sim.py @@ -0,0 +1,642 @@ +from ijutils import * +import java.lang.reflect +import flanagan.complex.ComplexMatrix as ComplexMatrix +import flanagan.math.Matrix as Matrix +import flanagan.complex.Complex as Complex +import org.jtransforms.fft.DoubleFFT_2D as DoubleFFT_2D +import math +from startup import ScriptUtils +import ij.plugin.filter.PlugInFilterRunner as PlugInFilterRunner +import ij.plugin.filter.ExtendedPlugInFilter as ExtendedPlugInFilter +import ij.plugin.filter.ExtendedPlugInFilter as ExtendedPlugInFilter +import java.lang.Thread as Thread + + +def new_array(type, *dimensions): + return java.lang.reflect.Array.newInstance(ScriptUtils.getPrimitiveType(type), *dimensions) + +def load_stack(title, file_list, show=False): + ip_list = [] + for f in file_list: + ip_list.append(open_image(expand_path(f))) + stack = create_stack(ip_list, title=title) + if show: + stack.show() + return stack + +def load_test_stack(title="Test", show=False, size=9): + file_list = [] + for index in range(40, 40+size): + file_list.append("{images}/TestObjAligner/i210517_0" + str(index) + "#001.tif") + return load_stack(title, file_list, show) + +def load_corr_stack(title="Corr", show=False): + file_list = [] + for index in range(40, 49): + file_list.append("{images}/TestObjAligner_corr/i210517_0" + str(index) + "#001.tif") + return load_stack(title, file_list, show) + +def complex_edge_filtering(imp, complex=True, g_sigma=3.0, g_resolution=1e-4, show=False, java_code=False): + if java_code: + get_context().getPluginManager().loadInitializePlugin("Align_ComplexEdgeFiltering.java") + complex_edge_filter = get_context().getClassByName("Align_ComplexEdgeFiltering").newInstance() + complex_edge_filter.setup(str(g_sigma)+","+str(complex)+","+str(show), imp) #Gaussian blur radius, Complex (True) or Real (False), show dialog = False + complex_edge_filter.run(imp.getProcessor()) + return complex_edge_filter.output + + gb = GaussianBlur() + sobel_r = [1, 0, -1, 2, 0, -2, 1, 0, -1] + sobel_i = [1, 2, 1, 0, 0, 0, -1, -2, -1] + + imp_r = imp.createImagePlus() + stack_r = ImageStack(imp.getWidth(), imp.getHeight()) + + if (complex): + imp_i = imp.createImagePlus() + stack_i = ImageStack(imp.getWidth(), imp.getHeight()) + for i in range(1, imp.getImageStackSize() + 1): + ip_r = imp.getStack().getProcessor(i).duplicate().convertToFloat() + # Gaussian blurring + gb.blurGaussian(ip_r, g_sigma, g_sigma, g_resolution) + ip_i = ip_r.duplicate() + # Sobel edge filtering + ip_r.convolve3x3(sobel_r) + ip_i.convolve3x3(sobel_i) + + stack_r.addSlice(imp.getStack().getSliceLabel(i), ip_r) + stack_i.addSlice(imp.getStack().getSliceLabel(i), ip_i) + IJ.showProgress(i, imp.getImageStackSize()) + + # imag + imp_i.setStack("EdgeImag_" + imp.getTitle(), stack_i); + imp_i.resetDisplayRange() + if show: + imp_i.show() + imp_i.updateAndDraw() + else: + imp_i = None + for i in range(1, imp.getImageStackSize() + 1): + ip_r = imp.getStack().getProcessor(i).duplicate().convertToFloat() + # Gaussian blurring + gb.blurGaussian(ip_r, g_sigma, g_sigma, g_resolution) + # Sobel edge filtering + ip_r.filter(ImageProcessor.FIND_EDGES) + stack_r.addSlice(imp.getStack().getSliceLabel(i), ip_r) + IJ.showProgress(i, imp.getImageStackSize()) + + # real + imp_r.setStack("EdgeReal_" + imp.getTitle(), stack_r) + imp_r.resetDisplayRange() + if show: + imp_r.show() + imp_r.updateAndDraw() + return [imp_r, imp_i] + +class TranslationFilter(ExtendedPlugInFilter): + def __init__(self): + self.shifts=None + self.flags = (self.DOES_ALL-self.DOES_RGB)|self.DOES_STACKS|self.NO_CHANGES|self.FINAL_PROCESSING + self.imp=None + self.output = None + self.translated = None + self.pifr = None + self.nbslices = 0 + self.processed = 0 + + def setup(self, arg, imp): + if "final"==arg: + self.output.setStack("REG_" + self.imp.getTitle(), self.translated) + return self.DONE + else: + if self.imp is None: + self.imp = imp; + return self.flags; + + def showDialog(self,imp, command, pfr): + self.pifr = pfr + return flags + + # Called by ImageJ to set the number of calls to run(ip) corresponding to 100% of the progress bar + def setNPasses(self, nPasses): + self.nbslices = nPasses; + self.output = self.imp.createImagePlus(); + self.translated = ImageStack(self.imp.getWidth(), self.imp.getHeight(), self.nbslices) + + #Process a FloatProcessor (with the CONVERT_TO_FLOAT flag, ImageJ does the conversion to float). + # Called by ImageJ for each stack slice (when processing a full stack); for RGB also called once for each color. */ + def run(self, ip): + if Thread.currentThread().isInterrupted(): + return + thisone = self.pifr.getSliceNumber() + + nip = ip.duplicate().convertToFloat() + nip.setInterpolationMethod(ImageProcessor.BICUBIC) + if len(self.shifts) != self.nbslices: + xoff, yoff = self.shifts[1][3], self.shifts[1][2] # translate all the frame by the same shifts + else: + xoff, yoff = self.shifts[thisone-1][3], self.shifts[thisone-1][2] + nip.translate(xoff, yoff) + + lbl = self.imp.getStack().getSliceLabel(thisone) + if lbl != None: + self.translated.addSlice(lbl, nip, thisone - 1) + else: + self.translated.addSlice("" + thisone, nip, thisone - 1) + + self.translated.deleteSlice(thisone + 1) + + self.processed+=1 + IJ.showProgress(self.processed, self.nbslices); + +def translate(stack, shifts, show=False, java_code=False): + WindowManager.setTempCurrentImage(stack) + if java_code: + get_context().getPluginManager().loadInitializePlugin("Align_TranslationFilter.java") + translation_filter = get_context().getClassByName("Align_TranslationFilter").newInstance() + translation_filter.imp = imp + translation_filter.shifts = shifts + pfr = PlugInFilterRunner(translation_filter, "", "" ) + ret = translation_filter.output + else: + translation_filter = TranslationFilter() + translation_filter.shifts = shifts + translation_filter.imp = stack + pfr = PlugInFilterRunner(translation_filter, "", "" ) + ret = translation_filter.output + if show: + ret.show() + ret.updateAndDraw() + return ret + + +def load_shifts(filename): + get_context().getPluginManager().loadInitializePlugin("ShiftsIO.java") + sio = get_context().getClassByName("ShiftsIO").newInstance() + return sio.load(expand_path(filename), "directshifts") + +def save_shifts(filename, shifts): + get_context().getPluginManager().loadInitializePlugin("ShiftsIO.java") + sio = get_context().getClassByName("ShiftsIO").newInstance() + sio.save(expand_path(filename), shifts, "directshifts") + + +def ip_to_fft_array_2d(ip): + pixels = ip.getPixels() + w = ip.getWidth() + h = ip.getHeight() + data = new_array('d', h, w) # new double[h][w] + for j in range(h): # (int j = 0; j < h; j++) + for i in range(w): # for (int i = 0; i < w; i++) + data[j][i] = pixels[j * w + i] + return data + + +def ip_to_fft_complex_array_2d(ip_r, ip_i): + pixels_r = ip_r.getPixels() + pixels_i = ip_i.getPixels() + w = ip_r.getWidth() + h = ip_r.getHeight() + data = new_array('d', h, 2 * w) # new double[h][2*w]; + for j in range(h): # (int j = 0; j < h; j++) + for i in range(w): # for (int i = 0; i < w; i++) + data[j][2 * i] = pixels_r[j * w + i] + data[j][2 * i + 1] = pixels_i[j * w + i]; + return data + +def fft_array_2d_to_complex_matrix(data, h, w): + m = ComplexMatrix(h,w) + for j in range(h): #for (int j = 0; j < h; j++) { + for i in range(w/2): # for (int i = 0; i <= w/2; i++) { + if (j > 0) and (i > 0) and (i < w/2): + m.setElement(j, i, Complex(data[j][2*i], data[j][2*i+1])) + m.setElement(h-j, w-i, Complex(data[j][2*i], -data[j][2*i+1])) + if (j == 0) and (i > 0) and (i < w/2): + m.setElement(0, i, Complex(data[0][2*i], data[0][2*i+1])) + m.setElement(0, w-i, Complex(data[0][2*i], -data[0][2*i+1])) + if (i == 0) and (j > 0) and (j < h/2): + m.setElement(j,0, Complex(data[j][0], data[j][1])) + m.setElement(h-j, 0, Complex(data[j][0], -data[j][1])) + m.setElement(j, w/2, Complex(data[h-j][1], -data[h-j][0])) + m.setElement(h-j, w/2, Complex(data[h-j][1], data[h-j][0])) + if (j == 0) and (i == 0): + m.setElement(0, 0, Complex(data[0][0], 0)); + if (j == 0) and (i == w/2): + m.setElement(0, w/2, Complex(data[0][1], 0)); + if (j == h/2) and (i == 0): + m.setElement(h/2, 0, Complex(data[h/2][0], 0)); + if (j == h/2) and (i == w/2): + m.setElement(h/2, w/2, Complex(data[h/2][1], 0)); + return m + + +def fft_complex_array_2d_to_complex_matrix(data, h, w): + m = ComplexMatrix(h,w); + for j in range(h): #for (int j = 0; j < h; j++) { + for i in range(w): # for (int i = 0; i < w; i++) { + m.setElement(j,i, Complex(data[j][2*i], data[j][2*i+1])) + return m + +def complex_matrix_to_fft_array_2d(m): + w = m.getNcol() + h = m.getNrow() + data = new_array('d', h,w) #new double[h][w]; + for j in range(h): #for (int j = 0; j < h; j++) { + for i in range(w): #for (int i = 0; i <= w/2; i++) { + if (j > 0) and (i > 0) and (i < w/2): + data[j][2*i] = m.getElementReference(j,i).getReal() + data[j][2*i+1] = m.getElementReference(j,i).getImag() + if (j == 0) and (i > 0) and (i < w/2): + data[0][2*i] = m.getElementReference(0,i).getReal() + data[0][2*i+1] = m.getEementReference(0,i).getImag() + if (i == 0) and (j > 0) and (j < h/2): + data[j][0] = m.getElementReference(j,0).getReal() + data[j][1] = m.getElementReference(j,0).getImag() + data[h-j][1] = m.getElementReference(j,w/2).getReal() + data[h-j][0] = m.getElementReference(h-j,w/2).getImag() + if (j == 0) and (i == 0): + data[0][0] = m.getElementReference(0,0).getReal() + if (j == 0) and (i == w/2): + data[0][1] = m.getElementReference(0,w/2).getReal() + if (j == h/2) and (i == 0): + data[h/2][0] = m.getElementReference(h/2,0).getReal() + if (j == h/2) and ( i == w/2): + data[h/2][1] = m.getElementReference(h/2,w/2).getReal() + return data + + +# convert a Complex Matrix into an 2d real part array data[0][][] and 2d imaginary part data[1][][] +def complex_matrix_to_real_array_2d(m): + w = m.getNcol() + h = m.getNrow() + data = new_array('d', 2,h,w) #new double[2][h][w]; + for j in range(h): #for (int j = 0; j < h; j++) { + for i in range(w): #for (int i = 0; i < w; i++) { + data[0][j][i] = m.getElementReference(j,i).getReal() + data[1][j][i] = m.getElementReference(j,i).getImag() + return data; + + +def compute_fft(imp_r, imp_i, roi): + slices = imp_r.getStackSize() + ffts = java.lang.reflect.Array.newInstance(ComplexMatrix, slices) # new ComplexMatrix[slices] + for i in range(1, slices + 1): + if imp_i is None: + ip = imp_r.getStack().getProcessor(i) + ip.setRoi(roi) + curr = ip.crop().convertToFloat(); + data = ip_to_fft_array_2d(curr) + ffts[i - 1] = fft2(data) + else: + ip1 = imp_r.getStack().getProcessor(i) + ip1.setRoi(roi) + curr_r = ip1.crop().convertToFloat() + ip2 = imp_i.getStack().getProcessor(i) + ip2.setRoi(roi) + curr_i = ip2.crop().convertToFloat() + data = ip_to_fft_complex_array_2d(curr_r, curr_i) + ffts[i - 1] = cfft2(data) + IJ.showProgress(i, slices) + return ffts + + +def element_product(a, b): + nr = a.getNrow() + nc = a.getNcol() + res = ComplexMatrix(nr, nc) + for j in range(nr): # (int j = 0; j < nr; j++) { + for i in range(nc): # (int i = 0; i < nc; i++) { + res.setElement(j, i, a.getElementReference(j, i).times(b.getElementReference(j, i))) + return res; + + +def fft_shift(complex_matrix): + nc = complex_matrix.getNcol() + nr = complex_matrix.getNrow() + out = ComplexMatrix(nr, nc) + midi = int(math.floor(nc / 2.0)) + offi = int(math.ceil(nc / 2.0)) + midj = int(math.floor(nr / 2.0)) + offj = int(math.ceil(nr / 2.0)) + for j in range(nr): # for (int j = 0; j < nr; j ++){ + for i in range(nc): # for (int i = 0; i < nc; i++) { + if j < midj: + if i < midi: + out.setElement(j, i, complex_matrix.getElementReference(j + offj, i + offi)) + else: + out.setElement(j, i, complex_matrix.getElementReference(j + offj, i - midi)) + else: + if i < midi: + out.setElement(j, i, complex_matrix.getElementReference(j - midj, i + offi)) + else: + out.setElement(j, i, complex_matrix.getElementReference(j - midj, i - midi)) + return out + + +def ifft_shift(complex_matrix): + nc = complex_matrix.getNcol() + nr = complex_matrix.getNrow() + out = ComplexMatrix(nr, nc) + midi = int(math.ceil(nc / 2.0)) + offi = int(math.floor(nc / 2.0)) + midj = int(math.ceil(nr / 2.0)) + offj = int(math.floor(nr / 2.0)) + + for j in range(nr): # (int j = 0; j < nr; j ++){ + for i in range(nc): # for (int i = 0; i < nc; i++) { + if j < midj: + if i < midi: + out.setElement(j, i, complex_matrix.getElementReference(j + offj, i + offi)) + else: + out.setElement(j, i, complex_matrix.getElementReference(j + offj, i - midi)) + else: + if i < midi: + out.setElement(j, i, complex_matrix.getElementReference(j - midj, i + offi)) + else: + out.setElement(j, i, complex_matrix.getElementReference(j - midj, i - midi)) + return out; + + +def ifft_shift_real(matrix): + nc = matrix.getNcol() + nr = matrix.getNrow() + out = Matrix (nr, nc) + + midi = int(math.ceil(nc/2.0)) + offi = int(math.floor(nc/2.0)) + midj = int(math.ceil(nr/2.0)) + offj = int(math.floor(nr/2.0)) + + for j in range(nr): # for (int j = 0; j < nr; j ++){ + for i in range(nc): #for (int i = 0; i < nc; i++) { + if j < midj: + if i < midi: + out.setElement(j, i, matrix.getElement(j+offj, i+offi)) + else: + out.setElement(j, i, matrix.getElement(j+offj, i-midi)) + else: + if i < midi: + out.setElement(j, i, matrix.getElement(j-midj, i+offi)) + else: + out.setElement(j, i, matrix.getElement(j-midj, i-midi)) + return out + + + +# compute 2D fft from an image +def fft2(data): + h =len(data) + w = len(data[0]) + fft = DoubleFFT_2D(h, w) + fft.realForward(data) + return fft_array_2d_to_complex_matrix(data, h, w) + +# compute complex 2D fft from an image +def cfft2(data): + h = len(data) + w = len(data[0]) + fft = DoubleFFT_2D(h, w/2) + fft.complexForward(data) + return fft_complex_array_2d_to_complex_matrix(data, h, w/2) +# compute inverse 2D fft from a complex matrix +def ifft2(m): + w = m.getNcol() + h = m.getNrow() + fft = DoubleFFT_2D(h, w) + data = complex_matrix_to_fft_array_2d(m) + fft.realInverse(data, True) + return data + +# compute complex inverse 2D fft from a complex matrix +def cifft2(m): + w = m.getNcol() + h = m.getNrow() + fft = DoubleFFT_2D(h, w) + data = new_array('d', h, 2 * w) # new double[h][2*w]; + for j in range(h): # for (int j=0; j max: + max = m.getElementReference(j, i).abs() + realmax = m.getElementReference(j, i).getReal() + imagmax = m.getElementReference(j, i).getImag() + rmax = j + cmax = i + res = new_array("d", 5) + res[0] = math.sqrt(realmax * realmax + imagmax * imagmax) + res[1] = rmax + res[2] = cmax + res[3] = realmax + res[4] = imagmax + return res; + + +def sum_square_abs(m): + s = 0.0 + for j in range(m.getNrow()): # (int j = 0; j < m.getNrow(); j ++): + for i in range(m.getNcol()): # for (int i = 0; i < m.getNcol(); i++): + s += m.getElementReference(j, i).squareAbs(); + return s; + + +def dftups(complex_matrix, nor, noc, roff, coff, usfac): + # function out=dftups(in,nor,noc,usfac,roff,coff); + # Upsampled DFT by matrix multiplies, can compute an upsampled DFT in justa small region. + # usfac Upsampling factor (default usfac = 1) + # [nor,noc] Number of pixels in the output upsampled DFT, in + # units of upsampled pixels (default = size(in)) + # roff, coff Row and column offsets, allow to shift the output array to + # a region of interest on the DFT (default = 0) + # Recieves DC in upper left corner, image center must be in (1,1) + # Loic Le Guyader - Jun 11, 2011 Java version for ImageJ plugin + # Manuel Guizar - Dec 13, 2007 + # Modified from dftus, by J.R. Fienup 7/31/06 + + # This code is intended to provide the same result as if the following + # operations were performed + # - Embed the array "in" in an array that is usfac times larger in each + # dimension. ifftshift to bring the center of the image to (1,1). + # - Take the FFT of the larger array + # - Extract an [nor, noc] region of the result. Starting with the + # [roff+1 coff+1] element. + + # It achieves this result by computing the DFT in the output array without + # the need to zeropad. Much faster and memory efficient than the + # zero-padded FFT approach if [nor noc] are much smaller than [nr*usfac nc*usfac] + + nr = complex_matrix.getNrow() + nc = complex_matrix.getNcol() + # Compute kernels and obtain DFT by matrix products + amplitude = -2.0 * math.pi / (nc * usfac) + nor,noc=int(nor),int(noc) + u = Matrix(nc, 1) + for i in range(nc): # (int i = 0; i < nc; i++) { + u.setElement(i, 0, i - math.floor(nc / 2.0)) + u = ifft_shift_real(u) + + v = Matrix(1, noc) + for i in range(noc): # for (int i = 0; i < noc; i++) { + v.setElement(0, i, i - coff) + + phase = u.times(v) + kernc = ComplexMatrix(nc, noc) + for j in range(nc): # for (int j = 0; j < nc; j++) { + for i in range(noc): # for (int i = 0; i < noc; i++) { + t = Complex() + t.polar(1.0, amplitude * phase.getElement(j, i)); + kernc.setElement(j, i, t) + + # ComplexMatrixPrint(kernc) + amplitude = -2.0 * math.pi / (nr * usfac) + + w = Matrix(nor, 1) + for i in range(nor): # for (int i = 0; i < nor; i++) { + w.setElement(i, 0, i - roff) + + x = Matrix(1, nr) + for i in range(nr): # for (int i = 0; i < nr; i++) { + x.setElement(0, i, i - math.floor(nr / 2.0)) + x = ifft_shift_real(x) + + nphase = w.times(x); + kernr = ComplexMatrix(nor, nr) + for j in range(nor): # for (int j = 0; j < nor; j++) { + for i in range(nr): # for (int i = 0; i < nr; i++) { + t = Complex(); + t.polar(1.0, amplitude * nphase.getElement(j, i)) + kernr.setElement(j, i, t) + # ComplexMatrixPrint(kernr); + return kernr.times(complex_matrix.times(kernc)) + +def dft_registration(ref, drifted, usfac): + m = ref.getNrow() + n = ref.getNcol() + output = new_array('d', 4) # new double[4] + + # First upsample by a factor of 2 to obtain initial estimate + # Embed Fourier data in a 2x larger array + mlarge = m * 2 + nlarge = n * 2 + large = ComplexMatrix(mlarge, nlarge) + c = fft_shift(element_product(ref, drifted.conjugate())) + + for j in range(m): # (int j = 0; j < m; j++): + for i in range(n): # (int i = 0; i < n; i++): + large.setElement(int(j + m - math.floor(m / 2.0)), int(i + n - math.floor(n / 2.0)), c.getElementReference(j, i)) + + # Compute crosscorrelation and locate the peak + CC = cifft2(ifft_shift(large)); + peak = c_find_peak(CC); # max, r, c, max_r, max_c + # Obtain shift in original pixel grid from the position of the + # crosscorrelation peak + if peak[1] > m: + peak[1] = peak[1] - mlarge; + if peak[2] > n: + peak[2] = peak[2] - nlarge; + # If upsampling > 2, then refine estimate with matrix multiply DFT + if usfac > 2: + # DFT computation + # Initial shift estimate in upsampled grid + row_shift = round(peak[1] / 2.0 * usfac) / usfac + col_shift = round(peak[2] / 2.0 * usfac) / usfac + dftshift = math.floor(math.ceil(usfac * 1.5) / 2) # Center of output array at dftshift+1 + # Matrix multiply DFT around the current shift estimate + cm = element_product(drifted, ref.conjugate()) + nCC = dftups(cm, math.ceil(usfac * 1.5), math.ceil(usfac * 1.5), \ + dftshift - row_shift * usfac, dftshift - col_shift * usfac, usfac) + nCC = nCC.times(1.0 / (m * n * usfac * usfac)).conjugate() + # Locate maximum and map back to original pixel grid + npeak = c_find_peak(nCC) # max_r, max_i, r, c + mrg00 = dftups(element_product(ref, ref.conjugate()), 1, 1, 0, 0, usfac) + rg00 = mrg00.getElementReference(0, 0).abs() / (m * n * usfac * usfac) + mrf00 = dftups(element_product(drifted, drifted.conjugate()), 1, 1, 0, 0, usfac) + rf00 = mrf00.getElementReference(0, 0).abs() / (m * n * usfac * usfac) + npeak[1] = npeak[1] - dftshift + npeak[2] = npeak[2] - dftshift + output[0] = math.sqrt(abs(1.0 - npeak[0] * npeak[0] / (rg00 * rf00))) # error + output[1] = math.atan2(npeak[4], npeak[3]) # diffphase + output[2] = row_shift + npeak[1] / usfac # delta row + output[3] = col_shift + npeak[2] / usfac # delta col + else: + # If upsampling = 2, no additional pixel shift refinement + rg00 = sum_square_abs(ref) / (mlarge * nlarge) + rf00 = sum_square_abs(drifted) / (mlarge * nlarge) + output[0] = math.sqrt(abs(1.0 - peak[0] * peak[0] / (rg00 * rf00))) # error + output[1] = math.atan2(peak[4], peak[3]) # diffphase + output[2] = peak[1] / 2.0 # delta row + output[3] = peak[2] / 2.0 # delta col + return output + + +def calculate_shifts(imp_r, imp_i, roi, upscale_factor=100, reference_slide=1, java_code=False): + if roi is None or roi.bounds.minX <0 or roi.bounds.minY<0 or roi.bounds.maxX>=imp_r.width or roi.bounds.maxY>=imp_r.height: + raise Exception("Invalid roi: " + str(roi)) + if java_code: + get_context().getPluginManager().loadInitializePlugin("Align_ComputeShifts2.java") + compute_shifts_filter = get_context().getClassByName("Align_ComputeShifts2").newInstance() + compute_shifts_filter.setup(upscale_factor, False, imp_r, imp_i, 1, roi) + compute_shifts_filter.run(None) + return compute_shifts_filter.shifts + + IJ.showStatus("1/2 Perform FFT of each slice") + ffts = compute_fft(imp_r, imp_i, roi) + + # calculate shifts + IJ.showStatus("2/2 Calculate shifts between slices") + + shifts = new_array('d', len(ffts), 6) # new double[ffts.length][6]; + for i in range(len(ffts)): # (int i = 0; i < ffts.length; i++): + shifts[i][0] = reference_slide + shifts[i][1] = i + 1 + temp = dft_registration(ffts[reference_slide - 1], ffts[i], upscale_factor) + shifts[i][2] = temp[2] + shifts[i][3] = temp[3] + shifts[i][4] = temp[0] + shifts[i][5] = temp[1] + IJ.showProgress(i + 1, len(ffts))\ + return shifts # [ref, drifted, dr, dc, error, diffphase] + +def to_ip(obj): + if is_string(obj): + obj = open_image(obj) + else: + if type(obj) == Data: + obj = obj.toBufferedImage(False) + if type(obj) == BufferedImage: + obj = load_image(obj) + return obj + +def calculate_shift(ref,img, roi, g_sigma=3.0, upscale_factor=100): + ref = to_ip(ref) + img = to_ip(img) + stack = create_stack([ref,img]) + ipr, ipi = complex_edge_filtering(stack, g_sigma=g_sigma, show=False) + shifts = calculate_shifts(ipr, ipi, roi, upscale_factor=upscale_factor, java_code=True) + xoff, yoff = shifts[1][3], shifts[1][2] + error, diffphase = shifts[1][4], shifts[1][5] + return xoff, yoff, error, diffphase + + +roi=Roi(256,0,128,128) +stack = load_test_stack(show=False, size=9) +ipr, ipi = complex_edge_filtering(stack, show=False) +shifts = calculate_shifts(ipr, ipi, roi, java_code=True) +#shifts= load_shifts("{images}/TestObjAligner/shifts.mat") +#stack = load_test_stack(show=True) +r=translate(stack, shifts, show=True) \ No newline at end of file diff --git a/script/jep/testdevs.py b/script/jep/testdevs.py new file mode 100644 index 0000000..195d2a5 --- /dev/null +++ b/script/jep/testdevs.py @@ -0,0 +1,45 @@ +from jep import jproxy +import random + +class WritableScalar(): + def write(self, value): + print ("Write: ", value) + +class ReadableScalar(): + def read(self): + print ("Read") + return random.random() + + +class ReadableWaveform(): + def getSize(self): + return 20 + + def read(self): + ret = [] + for i in range (self.getSize()): + ret.append(random.random()) + return ret + +class ReadableImage(): + def read(self): + ret = [] + for i in range (self.getHeight()): + ret.append([random.random()] * self.getWidth()) + return to_array(ret, 'd') + + def getWidth(self): + return 80 + + def getHeight(self): + return 40 + + + +ws1 = jproxy(WritableScalar(), ['ch.psi.pshell.device.Writable']) +rs1 = jproxy(ReadableScalar(), ['ch.psi.pshell.device.Readable']) +rw1 = jproxy(ReadableWaveform(), ['ch.psi.pshell.device.Readable$ReadableArray']) +ri1 = jproxy(ReadableImage(),['ch.psi.pshell.device.Readable$ReadableMatrix']) + +tscan(rs1, 10, 0.1) +lscan(ws1, rs1, 0.0, 10.0, 10) \ No newline at end of file diff --git a/script/local.py b/script/local.py index 5581d57..67abb6c 100755 --- a/script/local.py +++ b/script/local.py @@ -1,10 +1,45 @@ +# TESTIOC:TESTSINUS:SinCalc +# TESTIOC:TESTWF2:MyWF +# TESTIOC:TESTCALCOUT:Input +# TESTIOC:TESTCALCOUT:Output #get_context().dataManager.provider.embeddedAtributes = False #get_context().dataManager.provider.ADD_ATTRIBUTE_FILE_TIMESTAMP = True ################################################################################################### # EPICS utilities -################################################################################################### +############## +##################################################################################### + +if get_context().getScriptType()==ScriptType.cpy: + def on_ctrl_cmd(cmd): + global RE + print ("Control command: ", cmd) + if cmd=="abort": + if "RE" in globals(): + if RE.state not in ['idle','paused', 'pausing']: + print ("Run Engine pause request") + RE.request_pause() + abort() + +def otf(mode="ENERGY", e1=None, e2=None, beta1=None, beta2=None, theta1=None, theta2=None, \ + time=1.0, modulo=1, turn_off_beam=False): + """ + mode: "ENERGY" or "AMNGLE" + """ + run("otf", {"E1":450.0, "E2":460.0}) + run("otf", { + "MODE":mode, \ + "E1":float(e1) if e1 is not None else None, \ + "E2":float(e2) if e2 is not None else None, \ + "BETA1":float(beta1) if beta1 is not None else None, \ + "BETA2":float(beta2) if beta2 is not None else None, \ + "THETA1":float(theta1) if theta1 is not None else None, \ + "THETA2":float(theta2) if theta2 is not None else None, \ + "TIME":float(time), \ + "MODULO":int(modulo), \ + "ENDSCAN":turn_off_beam, \ + }) def caget_str(ch): return ''.join((chr(i) if i else "") for i in caget(ch)) @@ -73,17 +108,47 @@ def on_command_started(info): def on_command_finished(info): pass #print "Finished: " + str(info.script) + " Error: " + str(info.error) def on_change_data_path(path): - print "Data path: " + str(path) + #print "Data path: " + str(path) + pass def on_session_started(id): - print "Start session: ", id + #print "Start session: ", id get_context().sessionManager.setMetadata("beamlineState", {"sin": sin.take(), "inp": inp.take()}) get_context().sessionManager.setMetadata("beamlineState2", {"sin": sin.take(), "inp": inp.take()}) def on_session_finished(id): - print "Stop session: ", id + #print "Stop session: ", id get_context().sessionManager.setMetadata("scientificMetadata",SCI_METADATA) get_context().sessionManager.setMetadata("scientificMetadata2",SCI_METADATA) + + +################################################################################################### +# Image filters +################################################################################################### + +class DataFilter(Filter.DataFilter): + def process(self, data): + data=data.copy() + data.mult(2.0) + return data + +data_filter = DataFilter("filter") +image.addListener(data_filter) +proc_data=ColormapAdapter("proc_data", data_filter) +add_device(proc_data, True) + + +class ImageFilter(Filter.ImageFilter): + def process(self, img): + return ImagingUtils.transpose(img) + +image_filter = ImageFilter("filter") +image.addListener(image_filter) +#proc_image=ColormapAdapter("proc_image", image_filter) +add_device(image_filter, True) + +image.refresh() + @@ -144,6 +209,7 @@ import random #State listener: cleanup can be made at end of execution def onStateChange(state, former, script): + #print state if state == State.Closing: #print "Closing the app" pass @@ -180,20 +246,26 @@ class SimulatedOutput(Writable): def write(self, value): pass + def getName(self): + return "sout" -class SimulatedInput(Readable): +#class SimulatedInput(Readable): +class SimulatedInput(ReadonlyRegisterBase): def __init__(self): self.x = 0.0 - def read(self): + #def read(self): + def doRead(self): self.x = self.x + 0.1 noise = (random.random() - 0.5) / 20.0 return math.sin(self.x) + noise - + def getName(self): + return "sinp" + sout = SimulatedOutput() sinp = SimulatedInput() - +add_device(sinp, True) #for m in mu, delta, gamma, eta, chi, phi: # m.setSpeed(m.config.defaultSpeed) #Controler Evenrt Listener @@ -632,6 +704,9 @@ scan_val.set([0.0,] * MAX_ARRAY_DEV_SIZE ) #cas6= CAS("PSHELL:var",sin) """ +#show_panel(master) + + def plot_numpy_array(na, title = None): plot( Convert.reshape(na.getData(),na.getDimensions()),title=title) @@ -646,7 +721,6 @@ def run_fda(file_name, arguments={}): - #Convex hull plots def clear_convex_hull_plot(title): plots = get_plots(title = title) @@ -893,4 +967,132 @@ class Counter(ReadonlyRegisterBase): add_device(Counter("counter"), True) - \ No newline at end of file + + +def trigger_scienta(): + """ + Trigger new acquisition + """ + scienta.start() + #scienta.waitNewImage(-1) + + + +################################################################################################### +# Handlig diagnostics +################################################################################################### + +################################################################################################### +# Handlig diagnostics +################################################################################################### + +class AcquisitionMode(Readable.ReadableString): + def read(self): + return str(scienta.getAcquisitionMode()) +_acquisition_mode=AcquisitionMode() + +class EnergyMode(Readable.ReadableString): + def read(self): + return str(scienta.getEnergyMode()) +_energy_mode=EnergyMode() + +class LensMode(Readable.ReadableString): + def read(self): + return str(scienta.getLensMode()) +_lens_mode=LensMode() + +class DetectorMode(Readable.ReadableString): + def read(self): + return str(scienta.getDetectorMode()) +_detector_mode=DetectorMode() + +class PassEnergy(Readable): + def read(self): + return scienta.getPassEnergy() +_pass_energy=PassEnergy() + +class ElementSet(Readable.ReadableString): + def read(self): + return str(scienta.getElementSet()) +_element_set=ElementSet() + +diag_channels = [] + +diag_channels.append(scienta.lowEnergy) +diag_channels.append(scienta.centerEnergy) +diag_channels.append(scienta.highEnergy) +diag_channels.append(scienta.energyWidth) + +diag_channels.append(_acquisition_mode) +diag_channels.append(_energy_mode) +diag_channels.append(_lens_mode) +diag_channels.append(_detector_mode) +diag_channels.append(_pass_energy) +diag_channels.append(_element_set) + + +diag_channels = sorted(diag_channels, key=lambda channel: channel.name) + +def get_diag_name(diag): + return ch.psi.utils.Str.toTitleCase(diag.getName()).replace(" ", "").replace("Readback", "") + +def print_diag(): + for f in diag_channels: + print "%-25s %s" % (get_diag_name(f) , str(f.read())) + +def create_diag_datasets(parent = None): + if parent is None: + parent = get_exec_pars().group + group = parent + "attrs/" + for f in diag_channels: + create_dataset(group+get_diag_name(f) , 's' if (issubclass(type(f), Readable.ReadableString)) else 'd') + +def append_diag_datasets(parent = None): + if parent is None: + parent = get_exec_pars().group + group = parent + "attrs/" + for f in diag_channels: + try: + x = f.read() + if x is None: + x = '' if (type(f) is ch.psi.pshell.epics.ChannelString) else float('nan') + append_dataset(group+get_diag_name(f), x) + except: + log("Error sampling " + str(get_diag_name(f)) + ": " + str(sys.exc_info()[1])) + + +def handle_diagnostics(rec): + #if beam_ok: + if get_exec_pars().save: + #Saving only once the diag data + if rec.index == 0: + create_diag_datasets() + append_diag_datasets() + + +def scan_2d(range_x, range_y, steps_x, steps_y, exposures=None, settling_time=0.1, zigzag=True, compression=False, dry_run=False): + if type(range_x)==float: range_x =[-range_x, range_x] + if type(range_y)==float: range_y =[-range_y, range_y] + return run("templates/Scan2D", { \ + "DRY_RUN": dry_run, \ + "RANGE_X": range_x, \ + "RANGE_Y": range_y, \ + "STEPS": [steps_x, steps_y], \ + "EXPOSURES": exposures, \ + "SETTLING_TIME": settling_time, \ + "ZIGZAG": zigzag, \ + "COMPRESSION": compression + }) + + +""" +add_device(PipelineStream("p","localhost:8889", "simulation_sp"), True) +p.monitored=True + +s=p.createSubsampled(1.0) +#show_panel(s) +show_panel(p) +""" + +print "Finished initialization" + \ No newline at end of file diff --git a/script/otf.py b/script/otf.py new file mode 100644 index 0000000..9f6dbf4 --- /dev/null +++ b/script/otf.py @@ -0,0 +1,103 @@ +#Debugging +if get_exec_pars().innerArgs is None: + E1 = 975 + E2 = 985 + TIME = 1 #min + DELAY = 0.0 #s + MODE = None #'LINEAR' #'CIRC +' + OFFSET = None + NAME = 'predefined' + ALPHA= None #0 + + + +print "\nStart energy scan..." +print E1,"eV ->",E2,"eV,",TIME,"min duration,",DELAY,"sec delay,",str(MODE),(str(ALPHA)+"deg") if (MODE=="LINEAR") else "" + +set_exec_pars(reset=True, name= NAME) + + + +call_mscan=True +move_mono=False # if true, X-Tremeo runs, if false channel defiend under Ch runs + + +#Scan +print "Start OTF" +scan_completed = False + +try: + while True: + waiting = True + + class Time(Readable): + def __init__(self): + self.start = time.time() + def read(self): + return time.time()-self.start + tm = Time() + + class norm_tey(Readable): + def read(self): + return float(cadc1.take())/float(cadc2.take()) + + class norm_diode(Readable): + def read(self): + return float(cadc3.take())/float(cadc2.take()) + # define channels for PGM paramters + + position=sin + + + snaps = () + diags = () #Must use cache because mscan evensts are called from monitor callback tread (or else async=False). Sensors are automatically handled. + sensors = [position,out] + + def monitoring_task(): + global scan_completed + time.sleep(3.0) + + scan_completed = True + print('Scan completed ' + str(get_exec_pars().currentScan)) + global scans + scans.append(get_exec_pars().currentScan) + get_exec_pars().currentScan.abort() + print('after abort ') + + def before_pass(pass_num, scan): + print "Starting scan: " + str(scan) + + monitoring_future = fork(monitoring_task)[0] + + print("Scanning...\n") + try: + if call_mscan==True: + print('call mscan ') + mscan( position, sensors, -1, None, \ + range="auto",domain_axis=position.name, + snaps=snaps, diags=diags, before_pass=before_pass) + else: + print('-------------------------------------') + print('DO NOT CALL mscan ') + print('-------------------------------------') + #endelse + finally: + print('.....mscan done ') + if not scan_completed: + print('... cancel monitoring_future..') + monitoring_future.cancel(True) + print('monitoring is done',monitoring_future.isDone()) + print "Finished Energy scan." + if after_sample(): #Repeat if id error and not ABORT_ON_ID_ERROR: + break + +except: + if not scan_completed: + print sys.exc_info() + raise + #endif +#endexcept +print('-------DONE --------------') +# Finally, if mon is not use +# return to initial position + diff --git a/script/outupdate.py b/script/outupdate.py index 7808f16..f9058a2 100644 --- a/script/outupdate.py +++ b/script/outupdate.py @@ -1,5 +1,5 @@ -for i in range(100): +for i in range(20): out.write(i) time.sleep(0.1) \ No newline at end of file diff --git a/script/queues/mxas.que b/script/queues/mxas.que new file mode 100644 index 0000000..c2ccf2a --- /dev/null +++ b/script/queues/mxas.que @@ -0,0 +1 @@ +[ [ [ false, "/sls/X05LA/Data1/x05laop/2021_10/20211024/ScanX_vsAngle_Energy1_XRF_NoMythen_10s_60deg_AudioTape_x5_vXX.xml", "", "Resume", "Aborted" ], [ false, "/sls/X05LA/Data1/x05laop/2021_10/20211024/ScanX_vsAngle_Energy2_XRF_NoMythen_10s_60deg_AudioTape_x5_vXX.xml", "", "Resume", "" ], [ false, "/sls/X05LA/Data1/x05laop/2021_10/20211024/ScanX_vsAngle_Energy1_XRF_WithMythen_10s_60deg_AudioTape_x5_vXX.xml", "", "Resume", "" ], [ false, "/sls/X05LA/Data1/x05laop/2021_10/20211024/ScanX_vsAngle_Energy2_XRF_WithMythen_10s_60deg_AudioTape_x5_vXX.xml", "", "Resume", "" ], [ false, "/sls/X05LA/Data1/x05laop/2021_10/20211024/ScanX_vsAngle_Energy1_XRF_WithMythen_10s_60deg_AudioTape_x5_vXX.xml", "", "Resume", "" ], [ false, "/sls/X05LA/Data1/x05laop/2021_10/20211027/XRD_XRF_otf_200ms_17995eV_XY_LYPC.xml", "", "Resume", "" ], [ false, "/sls/X05LA/Data1/x05laop/2021_10/20211027/XRD_XRF_otf_200ms_17995eV_XY_LYPC.xml", "", "Resume", "" ], [ true, "/sls/X05LA/Data1/x05laop/2021_10/20211027/XRD_XRF_otf_200ms_17995eV_XY_LYPC.xml", "", "Resume", "" ] ] ] \ No newline at end of file diff --git a/script/queues/q5.que b/script/queues/q5.que new file mode 100644 index 0000000..d8df839 --- /dev/null +++ b/script/queues/q5.que @@ -0,0 +1 @@ +[ [ [ true, "", "print 1", "Resume", "Success" ], [ true, "queues/q4.que", "", "Resume", "Success" ], [ true, "Test.xml", "", "Resume", "Success" ], [ true, "test/test0.py", "", "Resume", "Failure" ], [ true, "test/test1.py", "", "Resume", "Success" ], [ true, "", "sleep(5.0)", "Resume", "Success" ], [ true, "", "print 2", "Resume", "Success" ], [ true, "", "sleep(0.5)", "Resume", "Success" ], [ true, "", "print 3", "Resume", "Success" ], [ true, "", "sleep(0.5)", "Resume", "Success" ], [ true, "", "print 4", "Resume", "Success" ], [ true, "", "sleep(0.5)", "Resume", "Success" ], [ true, "", "print 5", "Resume", "Success" ], [ true, "", "i=0", "Resume", "Success" ], [ true, "queues/q6.que", "", "Resume", "Success" ], [ false, "queues/q6.que", "", "Resume", "Disabled" ], [ false, "queues/q6.que", "", "Resume", "Disabled" ], [ false, "queues/q6.que", "", "Resume", "Disabled" ] ] ] \ No newline at end of file diff --git a/script/queues/q6.que b/script/queues/q6.que new file mode 100644 index 0000000..8164f36 --- /dev/null +++ b/script/queues/q6.que @@ -0,0 +1 @@ +[ [ [ true, "", "i=i+1", "Resume", "" ], [ true, "", "print \"###>\"+str(i)", "Resume", "" ], [ true, "", "print 10", "Resume", "" ], [ true, "", "sleep(0.25)", "Resume", "" ], [ true, "", "print 20", "Resume", "" ], [ true, "", "sleep(0.25)", "Resume", "" ], [ true, "", "print 30", "Resume", "" ], [ true, "", "sleep(0.25)", "Resume", "" ], [ true, "", "print 40", "Resume", "" ], [ true, "", "sleep(0.25)", "Resume", "" ], [ true, "", "print 50", "Resume", "" ], [ true, "", "sleep(0.25)", "Resume", "" ], [ true, "", "print 60", "Resume", "" ], [ true, "", "print \"--->\"+str(i)", "Resume", "" ] ] ] \ No newline at end of file diff --git a/script/queues/qt.que b/script/queues/qt.que new file mode 100644 index 0000000..d52d895 --- /dev/null +++ b/script/queues/qt.que @@ -0,0 +1 @@ +[ [ [ true, "test/test1.py", "", "Abort", "Success", "00:00:01" ], [ true, "test/test3.py", "", "Resume", "Aborted", "00:00:02" ], [ true, "test/test3.py", "", "Resume", "", "00:00:03" ] ] ] \ No newline at end of file diff --git a/script/queues/tst.que b/script/queues/tst.que new file mode 100644 index 0000000..fae509c --- /dev/null +++ b/script/queues/tst.que @@ -0,0 +1 @@ +[ [ [ false, "test_manip.xml", "", "Resume", "" ], [ false, "test_function_script.xml", "", "Resume", "" ], [ true, "BugSuperXAS.xml", "", "Resume", "" ], [ false, "testy.xml", "", "Resume", "" ], [ false, "xxx.xml", "", "Resume", "" ], [ false, "test/BugPhenix.xml", "", "Resume", "" ], [ false, "test/test_manip.xml", "", "Resume", "" ], [ false, "test/test_function_script.xml", "", "Resume", "" ], [ true, "test/BugSuperXAS.xml", "", "Resume", "" ], [ false, "test/testy.xml", "", "Resume", "" ], [ false, "test/xxx.xml", "", "Resume", "" ], [ false, "test/BugPhenix.xml", "", "Resume", "" ] ] ] \ No newline at end of file diff --git a/script/runotf.py b/script/runotf.py new file mode 100644 index 0000000..8ea1206 --- /dev/null +++ b/script/runotf.py @@ -0,0 +1,30 @@ +import traceback +import imp +import os +import time +import string +import sys +import json + + +def otf(start, end, time, delay=0.0, mode = None, offset = None, alpha = None, name = None): + """ + """ + # print('in otf',name) + + if name is None: + name = get_exec_pars().name + + if len(name)> 38: + name = name[:38] + print('WARNING: Sample name too long. Name has been truncated.') + + #print('in otf, call run \n') + run("otf", {"E1":start, "E2":end, "TIME":time, "DELAY":float(delay), "MODE":mode, "OFFSET":(offset), "NAME":name, "ALPHA":float(alpha) if alpha is not None else None}) + print('in otf, after run \n') + + + +for i in range(5): + print "----- RUN " + str(i) + otf(i, i+1, 3, delay=0.0, mode = None, offset = None, alpha = None, name = "scan"+str(i)) \ No newline at end of file diff --git a/script/scitest/.DS_Store b/script/scitest/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + + + + + + + + + + + + + + + + + + + + + 5365.0 + 5460.0 + 2.0 + + + + 5461.0 + 5520.0 + 0.3 + + + + 3.83209893521 + 15.0 + 0.05 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -9.705 + + + -21.5176 + + + 1.0 + + + 1.0 + + + 70.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/script/test/BugJep2.py b/script/test/BugJep2.py new file mode 100644 index 0000000..08cf484 --- /dev/null +++ b/script/test/BugJep2.py @@ -0,0 +1,22 @@ +writables=inp +readables= (sin,out) +start = 0 +end =40 +steps =50 +latency=0.2 +relative=False +passes=1 +zigzag=False + +latency_ms=int(latency*1000) +writables=to_list(string_to_obj(writables)) +readables=to_list(string_to_obj(readables)) +#start=to_list(start) +#end=to_list(end) +if type(steps) is float or is_list(steps): + steps = to_list(steps) +scan = scans.LineScan(writables,readables, start, end , steps, relative, latency_ms, int(passes), zigzag) +processScanPars(scan, pars) +scan.start() +ret= scan.getResult() + diff --git a/script/test/BugPhenix.xml b/script/test/BugPhenix.xml new file mode 100644 index 0000000..b162062 --- /dev/null +++ b/script/test/BugPhenix.xml @@ -0,0 +1,733 @@ + + + + + + + + + + + + + + + + + + + + + + + 1.0 + 2.0 + 0.2 + + + + 2.0 + 3.0 + 0.3 + + + 4.0 + 5.0 + 0.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.0 + + + 1.0 + + + 1.0 + + + 1.0 + + + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/script/test/BugSuperXAS.xml b/script/test/BugSuperXAS.xml new file mode 100644 index 0000000..2bf4c79 --- /dev/null +++ b/script/test/BugSuperXAS.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + -8.0 + 0.0 + 1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/script/test/BugSuperXASOrig.xml b/script/test/BugSuperXASOrig.xml new file mode 100644 index 0000000..c396d94 --- /dev/null +++ b/script/test/BugSuperXASOrig.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + -8.0 + 0.0 + 0.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/script/test/DirectCamserverStream.py b/script/test/DirectCamserverStream.py new file mode 100644 index 0000000..1d7df36 --- /dev/null +++ b/script/test/DirectCamserverStream.py @@ -0,0 +1,6 @@ +st= Stream("st", "tcp://localhost:5554", SocketType.SUB) +m=st.addMatrix("image") +st.initialize() +st.start(True) + +show_panel(m) \ No newline at end of file diff --git a/script/test/LoadIJStack.py b/script/test/LoadIJStack.py new file mode 100644 index 0000000..f02cf59 --- /dev/null +++ b/script/test/LoadIJStack.py @@ -0,0 +1,8 @@ +from ijutils import * + + +ip_list = [] +for index in range(40,49): + ip_list.append(open_image(expand_path("{images}/TestObjAligner/i210517_0" + str(index) + "#001.tif"))) + +stack = create_stack(ip_list, title = "Test") diff --git a/script/test/TSTIMGSIM3.py b/script/test/TSTIMGSIM3.py new file mode 100644 index 0000000..3a41179 --- /dev/null +++ b/script/test/TSTIMGSIM3.py @@ -0,0 +1,32 @@ +import traceback +run ("imaging/sim") + +show=True + + + +stack = load_test_stack(show=False) +shifts= load_shifts("{images}/TestObjAligner/shifts.mat") + + +if show: + ret.show() + ret.updateAndDraw() +#stack = load_test_stack(show=True) +#get_context().getPluginManager().loadInitializePlugin("Align_TranslationFilter.java") +#translation_filter = get_context().getClassByName("Align_TranslationFilter").newInstance() +#shifts= load_shifts("{images}/TestObjAligner/shifts.mat") + +""" +translation_filter.shifts = shifts +pfr = PlugInFilterRunner(translation_filter, "", "" ) +ret = translation_filter.output +if show: + ret.show() + ret.updateAndDraw() +#stack = load_test_stack(show=False) +#r=translate(stack, shifts, True, java_code=True) +""" + + +#translate(stack, shifts, show=True, java_code=True) \ No newline at end of file diff --git a/script/test/Test2DCont.py b/script/test/Test2DCont.py new file mode 100644 index 0000000..8215643 --- /dev/null +++ b/script/test/Test2DCont.py @@ -0,0 +1,14 @@ +STEPS = 10 + +class Line(ReadonlyRegisterBase): + def doRead(self): + r1 = cscan(m1, (ai1), 0.0, 1.0 , steps=STEPS, save=False) + return r1[ai1] + + def getSize(self): + return STEPS+1 + +line=Line() + +lscan(ai2, line, 0, 10, 1.0) + diff --git a/script/test/TestArrayTestStats.py b/script/test/TestArrayTestStats.py new file mode 100644 index 0000000..61f178c --- /dev/null +++ b/script/test/TestArrayTestStats.py @@ -0,0 +1,24 @@ +#av=create_averager(arr,1,) +#print av.sum.read() +#tscan(av.sum, 10,0.1) + + + +#CHANNEL_NAME= "TESTIOC:TESTWF2:MyWF" +#tscan("ca://" + CHANNEL_NAME + "?op=sum", 10, 1.0) + + + +#av=create_averager(arr,1,) +#print av.sum.read() +#tscan(av.sum, 10,0.1) + + + +s = ArrayRegisterStats(arr.cache).sum +s.asyncUpdate=True +tscan((arr,s), 10,1.0) + + +#plot(ars.sum.read()) + diff --git a/script/test/TestAthosCamerasChannel.py b/script/test/TestAthosCamerasChannel.py new file mode 100644 index 0000000..0956e50 --- /dev/null +++ b/script/test/TestAthosCamerasChannel.py @@ -0,0 +1,10 @@ +class StringChannel(RegisterBase): + def doRead(self): + return self.val if hasattr(self, 'val') else "" + def doWrite(self, val): + self.val = val + + +reg=StringChannel() +reg.initialize() +cas3 = CAS("TESTCAS:CH", reg, 'string') \ No newline at end of file diff --git a/script/test/TestCPython.py b/script/test/TestCPython.py new file mode 100644 index 0000000..a04d851 --- /dev/null +++ b/script/test/TestCPython.py @@ -0,0 +1,47 @@ +run("CPython/wrapper") + +x=[0,1,2,3,4,5,6,7,8,9] +y=[1,2,3,6,9,6,3,2,1,0] +#(p, x_fit, y_fit, R2) = linfit(x,y) +#plot((y,y_fit), name=("data", "fit"),xdata=(x,x_fit)) + + +#x=to_array(x,'f') +#y=to_array(y,'f') + +def _from_npa(obj): + if isinstance(obj, jep.NDArray): + ret = obj.data + if len(obj.dimensions)>1: + ret=Convert.reshape(ret, obj.dimensions) + return ret + if isinstance(obj, java.util.List) or isinstance(obj,tuple) or isinstance(obj,list): + ret=[] + for i in range(len(obj)): + ret.append(_from_npa(obj[i])) + if isinstance(obj,tuple): + return type(ret) + return ret + return obj + +def _to_npa(obj): + if isinstance(obj, PyArray): + return to_npa(obj, dimensions = Arr.getShape(obj)) + if isinstance(obj, java.util.List) or isinstance(obj,tuple) or isinstance(obj,list): + ret=[] + for i in range(len(obj)): + ret.append(_to_npa(obj[i])) + if isinstance(obj,tuple): + return tuple(ret) + return ret + return obj + +def call_py(module, function, *args): + print _to_npa(args) + ret = call_jep(module, function, _to_npa(args)) + return _from_npa(ret) + +#ret = call_py("CPython/linfit", "linfit",x,y) +(p, x_fit, y_fit, R2) = call_py("CPython/linfit", "linfit",x,y) +#(p, x_fit, y_fit, R2) = linfit(x,y) +plot((y,y_fit), name=("data", "fit"),xdata=(x,x_fit)) \ No newline at end of file diff --git a/script/test/TestCmd.py b/script/test/TestCmd.py new file mode 100644 index 0000000..71d4fd6 --- /dev/null +++ b/script/test/TestCmd.py @@ -0,0 +1 @@ +exec_cmd("ls") \ No newline at end of file diff --git a/script/test/TestCompositeScan.py b/script/test/TestCompositeScan.py new file mode 100644 index 0000000..391021c --- /dev/null +++ b/script/test/TestCompositeScan.py @@ -0,0 +1,31 @@ +################################################################################################### +# This is an option to implement a 2D continuous scan +################################################################################################### + +STEPS_M1 = 10 +STEPS_M2 = 5 + +class Sensor(ReadableArray): + def read(self): + r1 = cscan(m1, (ai1), 0.0, 1.0 , steps=STEPS_M1, save=False, display=False) + return r1[ai1] + + def getSize(self): + return STEPS_M1+1 + + def getName(self): + return "sensor" + +""" +p = plot(None, title="2d Plot")[0] +def after_read(record, scan): + if record.setpoints[1] == scan.getStart()[1]: + p.addSeries(LinePlotSeries(str(record[ao1]))) + p.getSeries(p.numberOfSeries-1).appendData(record[ao2], record[ai1]) +""" + +set_exec_pars(print_scan=True) + +#lscan(m2, Sensor(), 0, 1.0, STEPS_M2, after_read=after_read) +s1=Sensor() +lscan(m2, s1, 0, 1.0, STEPS_M2) \ No newline at end of file diff --git a/script/test/TestConfig.py b/script/test/TestConfig.py new file mode 100644 index 0000000..0f06ac8 --- /dev/null +++ b/script/test/TestConfig.py @@ -0,0 +1,24 @@ +#Jython BUG: Sometimes getConfig(self) won'' override ProcessVariableBase.getConfig. +#E.g.: energy.getUnit() fails (calls getConfig) +#But energy.getConfig() works + + +class Energy(ControlledVariableBase): + def __init__(self, name): + ControlledVariableBase.__init__(self, name, None) + self.setReadback(phi.readback) + + def doRead(self): + return phi.read() + + def doWrite(self, val): + print "Do something" + phi.write(val) + + def getConfig(self): + print "Get" + return phi.getConfig() + + + +add_device(Energy("energy",), True) \ No newline at end of file diff --git a/script/test/TestData.py b/script/test/TestData.py index 578093b..f4d13e7 100644 --- a/script/test/TestData.py +++ b/script/test/TestData.py @@ -4,7 +4,7 @@ #Creating a 1D dataset from an array -path="group/data1" +path="data1" data1d = [1.0, 2.0, 3.0, 4.0, 5.0] save_dataset(path, data1d) #Reading ii back @@ -15,7 +15,7 @@ plot(read) #Creating a 2D dataset from an array with some attributes data2d = [ [1.0, 2.0, 3.0, 4.0, 5.0], [2.0, 3.0, 4.0, 5.0, 6.0, ], [3.0, 4.0, 5.0, 6.0, 7.0]] -path="group/data2" +path="data2" save_dataset(path, data2d) set_attribute(path, "AttrString", "Value") set_attribute(path, "AttrInteger", 1) @@ -28,7 +28,7 @@ plot(read) #Creating a 3D dataset from an array data3d = [ [ [1,2,3,4,5], [2,3,4,5,6], [3,4,5,6,7]], [ [3,2,3,4,5], [4,3,4,5,6], [5,4,5,6,7]]] -path="group/data3" +path="data3" save_dataset(path, data3d) #Reading it back read =load_data(path,0) @@ -37,28 +37,28 @@ read =load_data(path,1) print read.tolist() #Creating a INT dataset adding elements one by one -path = "group/data4" +path = "data4" create_dataset(path, 'i') for i in range(10): append_dataset(path,i) #Creating a 2D data FLOAT dataset adding lines one by one -path = "group/data5" +path = "data5" create_dataset(path, 'd', False, (0,0)) for row in data2d: append_dataset(path, row) #Creating a Table (compund type) -path = "group/data6" +path = "data6" names = ["a", "b", "c", "d"] -types = ["d", "d", "d", "[d"] +table_types = ["d", "d", "d", "[d"] lenghts = [0,0,0,5] table = [ [1,2,3,[0,1,2,3,4]], [2,3,4,[3,4,5,6,7]], [3,4,5,[6,7,8,9,4]] ] -create_table(path, names, types, lenghts) +create_table(path, names, table_types, lenghts) for row in table: append_table(path, row) flush_data() @@ -68,11 +68,11 @@ print read #Writing scalars (datasets with rank 0) -save_dataset("group/val1", 1) -save_dataset("group/val2", 3.14) -save_dataset("group/val3", "test") -print load_data("group/val1") -print load_data("group/val2") -print load_data("group/val3") +save_dataset("val1", 1) +save_dataset("val2", 3.14) +save_dataset("val3", "test") +print load_data("val1") +print load_data("val2") +print load_data("val3") diff --git a/script/test/TestDataToImage.py b/script/test/TestDataToImage.py new file mode 100644 index 0000000..d1cfe08 --- /dev/null +++ b/script/test/TestDataToImage.py @@ -0,0 +1,14 @@ +scale_min=100 +scale_max=150 +colormap = Colormap.Temperature #Colormap.Grayscale + + +while(True): + try: + i=image.data.toBufferedImage(scale_min, scale_max, colormap, False) + show_panel(i) + except: + print sys.exc_info()[2] + break + time.sleep(1.0) + \ No newline at end of file diff --git a/script/test/TestDiscretePositionerSelector.py b/script/test/TestDiscretePositionerSelector.py new file mode 100644 index 0000000..09d07ae --- /dev/null +++ b/script/test/TestDiscretePositionerSelector.py @@ -0,0 +1,24 @@ +import ch.psi.utils.swing.SwingUtils as SwingUtils +import ch.psi.pshell.swing.DiscretePositionerPanel as DiscretePositionerPanel +import ch.psi.pshell.swing.DiscretePositionerSelector as DiscretePositionerSelector +import ch.psi.pshell.swing.RegisterPanel as RegisterPanel + +""" +setp = scienta.getChannelCtrl()+":LENS_MODE" +rbck = scienta.getChannelCtrl()+":LENS_MODE_RBV" + +dp = DiscretePositioner("lens_mode", setp, rbck) +dp.monitored=True +add_device(dp, True) + + +pn=DiscretePositionerSelector() +pn.setDevice(dp) +SwingUtils.showDialog(App.getInstance().mainFrame, dp.name, None, pn) + +show_panel(dp) +""" +reg=sin #scienta.getEnergyStepSize() +rp=RegisterPanel() +rp.setDevice(None) +SwingUtils.showDialog(App.getInstance().mainFrame, reg.name, None, rp) diff --git a/script/test/TestImgMeasure.py b/script/test/TestImgMeasure.py new file mode 100644 index 0000000..5f60bd6 --- /dev/null +++ b/script/test/TestImgMeasure.py @@ -0,0 +1,6 @@ +class Measurement (ImageMeasurement): + def calc(self, data): + return data.getRoi(Rectangle(10,10,20,20)).integrate(False) + + +tscan(Measurement(src2, "roi"), 10, 0.2) \ No newline at end of file diff --git a/script/test/TestIndexReadback.py b/script/test/TestIndexReadback.py new file mode 100644 index 0000000..edec291 --- /dev/null +++ b/script/test/TestIndexReadback.py @@ -0,0 +1,31 @@ + + +def after_read(rec,scan): + print rec[energy], rec[sin] + + + +#r = mscan(energy, sin.cache, after_read=after_read) + + +#Execute the scan: sample undefined number of samples until a condition is met, with auto range +scan_completed=False +def after_read(record, scan): + global scan_completed + print record[sin] + if motor.isReady(): + scan_completed=True + scan.abort() + +motor.move(0.0) +motor.moveAsync(3.0) +try: + r = mscan(motor.readback, [motor.readback, sin.cache], after_read=after_read, range=[0.0, 3.0], domain_axis=motor.readback.name) +except ScanAbortedException as ex: + if not scan_completed: raise +motor.moveAsync(0.0) + +print r[sin] + +#print r[0][sin] +print r[0][motor.readback] \ No newline at end of file diff --git a/script/test/TestJEP.py b/script/test/TestJEP.py new file mode 100644 index 0000000..b39f6af --- /dev/null +++ b/script/test/TestJEP.py @@ -0,0 +1,21 @@ +from jeputils import import_py + +import_py("CPython/linfit", "linfit") +import_py("CPython/gfitoff", "gfitoff") + +x=[0,1,2,3,4,5,6,7,8,9] +y=[1,2,3,6,9,6,3,2,1,0] +(p, x_fit, y_fit, R2) = linfit(x,y) +#print "Fit: ", (p, x_fit, y_fit, R2) +plot((y,y_fit), name=("data", "fit"),xdata=(x,x_fit)) + + +from mathutils import Gaussian +x=to_array([-200.30429237268825, -200.2650700434188, -200.22115208318002, -199.9457671375377, -199.86345548879072, -199.85213073174933, -199.35687977133284, -199.13811861090275, -197.97304970346386, -197.2952215624348, -195.09076092936948, -192.92276048970703, -191.96871876227698, -189.49577852322938, -187.9652790409825, -183.63756456925222, -180.04899765472996, -178.43839623242422, -174.07311671294445, -172.0410133577918, -165.90824309893102, -160.99771795989466, -159.30176653939253, -154.27688897558514, -152.0854103810786, -145.75652847587313, -140.80843828908465, -139.23982133191495, -134.27073891256106, -132.12649284133064, -125.95947209775511, -121.00309550337462, -119.26736932643232, -114.2706655484383, -112.07393889578914, -105.72295990367157, -100.8088439880125, -99.2034906238494, -94.30042325164636, -92.15010048151461, -85.92203653534293, -81.03913275494665, -79.27412793784428, -74.33487658582118, -72.06274362408762, -65.76562628131825, -60.91255356825276, -59.20334389560392, -54.33286972659312, -52.19387171350535, -45.94978737932291, -41.03014719193582, -39.301602568238906, -34.35572209014114, -32.04464301272608, -25.8221033382824, -20.922074315528747, -19.21590299233186, -14.31090212502093, -12.217203140101386, -5.9283722049240435, -0.9863587170369246, 0.7408048387279834, 5.71126832601389, 7.972628957879352, 14.204559894256546, 19.11839959633025, 20.8218087836657, 25.678748486941828, 27.822718344586864, 34.062659474970715, 38.9745656819391, 40.77409719734158, 45.72080631619803, 47.974156754056835, 54.23453768983539, 59.12020360609568, 60.77306570712026, 65.70734521458867, 67.8344660434617, 74.03187028154134, 78.96532114824849, 80.76070945985495, 85.74802197591286, 87.9140889204674, 94.18082276873524, 99.25790470037091, 100.68454787413205, 105.7213026221542, 107.79483801526698, 113.99555681638138, 119.0707052529143, 120.72715813056156, 125.77551384921307, 127.91257836719551, 134.2011330887875, 139.23043006997628, 140.71673537840158, 145.76288138835983, 147.80216629676042, 154.06420451405637, 159.0846626604798, 160.76183155710717, 165.73699067536242, 167.9265357747636, 173.96705069576544, 178.2522282751915, 179.9042617354548, 183.54586165856657, 185.23269803071796, 189.41678143751972, 191.87149157986588, 192.8741468985015, 195.0241934550453, 195.966634211846, 197.9821647518146, 198.99006812859284, 199.33202054855676, 199.91897441965887, 200.11536227958896, 200.22280936469997, 200.25181179127208],'d') +y=to_array([11.0, 6.0, 8.0, 5.0, 11.0, 7.0, 18.0, 11.0, 12.0, 10.0, 8.0, 6.0, 16.0, 4.0, 12.0, 9.0, 15.0, 14.0, 8.0, 20.0, 15.0, 8.0, 9.0, 11.0, 13.0, 12.0, 13.0, 15.0, 13.0, 20.0, 10.0, 7.0, 17.0, 11.0, 20.0, 13.0, 13.0, 23.0, 14.0, 10.0, 17.0, 15.0, 20.0, 16.0, 14.0, 13.0, 18.0, 22.0, 9.0, 20.0, 12.0, 14.0, 17.0, 19.0, 14.0, 14.0, 23.0, 19.0, 15.0, 20.0, 20.0, 21.0, 20.0, 23.0, 22.0, 15.0, 10.0, 17.0, 21.0, 15.0, 23.0, 23.0, 25.0, 18.0, 16.0, 21.0, 22.0, 16.0, 16.0, 14.0, 19.0, 20.0, 18.0, 20.0, 23.0, 13.0, 16.0, 20.0, 25.0, 15.0, 15.0, 17.0, 22.0, 26.0, 19.0, 30.0, 25.0, 17.0, 17.0, 23.0, 16.0, 27.0, 21.0, 21.0, 26.0, 27.0, 21.0, 17.0, 20.0, 20.0, 21.0, 19.0, 25.0, 19.0, 13.0, 23.0, 20.0, 20.0, 18.0, 20.0, 19.0, 25.0],'d') +[off, amp, com, sigma] = gfitoff(x, y, off=None, amp=None, com=None, sigma=None) +#print "Fit: ", [off, amp, com, sigma] +g = Gaussian(amp, com, sigma) +plot([y, [g.value(i)+off for i in x]], ["data", "fit"], xdata = x) + + diff --git a/script/test/TestLayout.py b/script/test/TestLayout.py index c9dec89..3228bf7 100755 --- a/script/test/TestLayout.py +++ b/script/test/TestLayout.py @@ -1,39 +1,16 @@ -tutorial_path = "src/main/assembly/help/Tutorial_py/" -run(tutorial_path+"SimulatedDevices") - import ch.psi.pshell.data.LayoutDefault -class LayoutParallelScan(ch.psi.pshell.data.LayoutDefault): +class MyLayout(ch.psi.pshell.data.LayoutDefault): def getDefaultGroup(self, scan): - return scan.readables[0].name + return str("test") +get_context().dataManager.setLayout(MyLayout()) -#get_context().pluginManager.addDynamicClass(LayoutParallel().getClass()) +tscan (sin,10,0.1) +writer = get_context().dataManager.provider.writer +o=writer.object() +o.createSoftLink("/test/sin", "/data/data1") +o.createHardLink("/test/sin", "/data/data2") - -#Does not work -#lay = LayoutParallelScan().getClass() -##print lay -#cls = Class.forName(lay.getCanonicalName(), True, lay.getClassLoader()) -#3print cls -#set_exec_pars(layout = "org.python.proxies.__main__$LayoutParallelScan$25") - - - -#Must restore the layout -#get_context().dataManager.setLayout(LayoutParallelScan()) - -set_exec_pars(layout = "Table") - - - -def scan1(): - print "scan1" - return lscan(ao1, ai1, 0, 40, 20, 0.1, title = "scan1") - -def scan2(): - print "scan2" - return lscan(ao2, ai2, 0, 40, 20, 0.1, title = "scan2") - - -parallelize(scan1, scan2) +o.createSoftLink("/test", "/link1") #Not supported +o.createHardLink("/test", "/link2") \ No newline at end of file diff --git a/script/test/TestMaster.py b/script/test/TestMaster.py new file mode 100644 index 0000000..8711801 --- /dev/null +++ b/script/test/TestMaster.py @@ -0,0 +1,7 @@ +master = MasterPositioner("master",mu, delta, gamma, eta) + +add_device(master, True) + +add_device(master.motorGroup, True) +show_panel(master) +show_panel(master.motorGroup) \ No newline at end of file diff --git a/script/test/TestMaster2.py b/script/test/TestMaster2.py new file mode 100644 index 0000000..f84ec66 --- /dev/null +++ b/script/test/TestMaster2.py @@ -0,0 +1,3 @@ +for i in frange(0.0, 1.0, 0.1): + master.move(i) + print mu.take(), delta.take(), gamma.take(), eta.take() diff --git a/script/test/TestMatrixPlotRenderer.py b/script/test/TestMatrixPlotRenderer.py new file mode 100644 index 0000000..29ab89e --- /dev/null +++ b/script/test/TestMatrixPlotRenderer.py @@ -0,0 +1,4 @@ + +def before_read(): + time.sleep(1.0) +tscan((sin,scienta.dataMatrix, det.dataMatrix), 5, 0.1, before_read=before_read, save=False, plot_types={scienta.dataMatrix:"ch.psi.pshell.plot.MatrixPlotRenderer"}) \ No newline at end of file diff --git a/script/test/TestMonit.py b/script/test/TestMonit.py new file mode 100644 index 0000000..e6dcadf --- /dev/null +++ b/script/test/TestMonit.py @@ -0,0 +1,23 @@ +def after_read(rec, scan): + if rec[sin] < 19.0: + rec.invalidate() + time.sleep(0.05) + print "Refused " + str(rec["sin"]) + " at " + str (time.time()- start) + else: + print "Acccepted " + str(rec["sin"]) + " at " + str (time.time()- start) + + +start= time.time() +r1 = mscan(sin, [sin, out],-1, 3, async=False, after_read=after_read) +print r1.getSize() +r2 = mscan(sin, [sin, out],-1, 3, async=True, after_read=after_read) +print r2.getSize() +r3 = mscan(sin, [sin, out],10, 4, async=False, after_read=after_read) +print r3.getSize() +r4 = mscan(sin, [sin, out],10, 4, async=True, after_read=after_read) +print r4.getSize() + +#r1 = lscan(energy, [sin],0, 1, 10, 0.1, after_read=after_read) + + +#r1 = tscan(sin, 50, 0.1, fixed_rate = True, domain_axis="Time", after_read=after_read) \ No newline at end of file diff --git a/script/test/TestMonit.txt b/script/test/TestMonit.txt new file mode 100644 index 0000000..a4d69ee --- /dev/null +++ b/script/test/TestMonit.txt @@ -0,0 +1,9 @@ +def after_read(rec, scan): + if rec["sin"] < 15.0: + rec.invalidate() + time.sleep(0.1) + + + + +r1 = mscan(sin, [sin], 25, after_read=after_read) \ No newline at end of file diff --git a/script/test/TestMonitor.py b/script/test/TestMonitor.py new file mode 100644 index 0000000..6cd67c6 --- /dev/null +++ b/script/test/TestMonitor.py @@ -0,0 +1,125 @@ +################################################################################################### +# Example of saving diagostics and snapshots during a scan +################################################################################################### + +DIAGS = [ai2] +SNAPS = [ai3, wf1] + +ret = lscan(m1, ai1, 0.0, 1.0, 4, diags=DIAGS, snaps=SNAPS) + +plot(ret.getSnap(wf1)) +plot(ret.getDiag(ai2)) + +#All devices can be directly indexd +for dev in [m1, ai1, ai2, wf1, ai3]: + print dev.name, " -> ", ret[dev] + + + +#Changing default folders +LayoutBase.PATH_SNAPS = "initial/" +LayoutBase.PATH_DIAGS = "status/" +LayoutBase.PATH_MONITORS = "events/" +LayoutBase.PATH_LOGS = "logbook/" + +ret = lscan(m1, ai1, 0.0, 1.0, 4, diags=DIAGS, snaps=SNAPS) + + + +ret = tscan(out, 10, 0.2, diags=[inp, arr], monitors=[inp, sin], snapshots=[inp, motor]) +#set_exec_pars(flush=True) +#print ret[out] +print ret[sin] +print ret["arr"] +print ret[motor] + + +#print ret.getDiag(sin) +#print ret.getDiag(arr) +#ret = tscan(out, 10, 0.2, monitors=[sin, arr]) +#plot(ret[sin][1], xdata=ret[sin][0]) +#plot(ret[out], xdata=ret.timestamps) + + + + +""" +class OtfValue(ReadonlyAsyncRegisterBase): + def __init__(self, name): + ReadonlyAsyncRegisterBase.__init__(self, name) + + def setValue(self, value,timestamp): + self.setCache(value,timestamp) + +class OTF(Otf): + def __init__(self, name): + Otf.__init__(self, name) + self.addComponent(OtfValue("Channel1")) + self.addComponent(OtfValue("Channel2")) + self.components[0].setValue(float("NaN"), long(time.time()*10e6)) + self.components[1].setValue(-1, long(time.time()*10e6)) + self.initialize() + self.interrupted = False + + def task(self): + start = time.time() + finished=False + while not self.interrupted and not finished: + time.sleep(0.1) + self.components[0].setValue(time.time(), long(time.time()*10e6)) + finished = (time.time()-start)> 5.0 + if not self.interrupted: + for i in range(20): + self.components[1].setValue(i, long(time.time()*10e6)) + self.getLogger().info("Finished OTF") + self.state=State.Ready + + def start(self): + self.state=State.Busy + self.interrupted = False + self.thread = fork(self.task) + + def abort(self): + self.interrupted = True + self.getLogger().info("Interrupting OTF thread") + join(self.thread) + +add_device(OTF("otf"), True) + +ret = tscan(out, 10, 0.2, monitors=[otf]) +""" +""" +class OTF(Otf): + def __init__(self, name): + Otf.__init__(self, name) + self.setCache(float("NaN"), long(time.time()*10e6)) + self.initialize() + self.interrupted = False + + def task(self): + start = time.time() + finished=False + while not self.interrupted and not finished: + time.sleep(0.1) + self.setCache(time.time(), long(time.time()*10e6)) + finished = (time.time()-start)> 5.0 + self.getLogger().info("Finished OTF") + self.state=State.Ready + + def start(self): + self.state=State.Busy + self.interrupted = False + self.thread = fork(self.task) + + def abort(self): + self.interrupted = True + self.getLogger().info("Interrupting OTF thread") + join(self.thread) + +add_device(OTF("otf"), True) + +ret = tscan(out, 10, 0.2, monitors=[otf]) +plot(ret[otf][1], xdata=ret[otf][0]) +""" + +set_return(None) \ No newline at end of file diff --git a/script/test/TestMultiDimens.py b/script/test/TestMultiDimens.py new file mode 100644 index 0000000..13bf768 --- /dev/null +++ b/script/test/TestMultiDimens.py @@ -0,0 +1,44 @@ +def append_dataset(path, data, index=None, type='d', shape=None): + """Append data to dataset. + + Args: + path(str): Path to dataset relative to the current persistence context root. + data(number or array or list): name of each column. + index(int or list, optional): if set then add the data in a specific position in the dataset. + If integer is the index in an array (data must be 1 order lower than dataset) + If a list, specifies the full coordinate for multidimensional datasets. + type(str, optional): array type 'b' = byte, 'h' = short, 'i' = int, 'l' = long, 'f' = float, + 'd' = double, 'c' = char, 's' = String, 'o' = Object + default: 'd' (convert data to array of doubles) + shape(list, optional): only valid if index is a list, provides the shape of the data array. + In this case data must be a flattened one-dimensional array. + Returns: + None + """ + data = to_array(data, type) + + if index is None: + get_context().dataManager.appendItem(path, data) + else: + if is_list(index): + if shape is None: + shape = [len(index)] + get_context().dataManager.setItem(path, data, index, shape) + else: + get_context().dataManager.setItem(path, data, index) + + +path = "data/data4d" +data3d = [ [ [1,2,3,4,5], [2,3,4,5,6], [3,4,5,6,7], [4,5,6,7,8]], + [ [2,2,3,4,5], [3,3,4,5,6], [5,4,5,6,7], [7,5,6,7,8]], + [ [3,2,3,4,5], [4,3,4,5,6], [6,4,5,6,7], [8,5,6,7,8]] ] + +plot(data3d) + +data = flatten(to_array(data3d,'i)) + + + +#save_dataset(path, data4d) +create_dataset(path, 'i', dimensions = [2, 3, 4, 5]) +append_dataset(path, data, index = [0,0,0,0], type = 'i', shape=[1, 3, 4, 5]) \ No newline at end of file diff --git a/script/test/TestParallelJEP.py b/script/test/TestParallelJEP.py new file mode 100644 index 0000000..bf577c7 --- /dev/null +++ b/script/test/TestParallelJEP.py @@ -0,0 +1,30 @@ +from jeputils import import_py +from mathutils import Gaussian +import traceback + +import_py("CPython/linfit", "linfit") +import_py("CPython/gfitoff", "gfitoff") + + +def task(index): + try: + print index + x=[0,1,2,3,4,5,6,7,8,9] + y=[1,2,3,6,9,6,3,2,1,0] + (p, x_fit, y_fit, R2) = linfit(x,y) + #print "Fit: ", (p, x_fit, y_fit, R2) + plot((y,y_fit), name=("data", "fit"),xdata=(x,x_fit), title=str(index)) + + + + x=to_array([-200.30429237268825, -200.2650700434188, -200.22115208318002, -199.9457671375377, -199.86345548879072, -199.85213073174933, -199.35687977133284, -199.13811861090275, -197.97304970346386, -197.2952215624348, -195.09076092936948, -192.92276048970703, -191.96871876227698, -189.49577852322938, -187.9652790409825, -183.63756456925222, -180.04899765472996, -178.43839623242422, -174.07311671294445, -172.0410133577918, -165.90824309893102, -160.99771795989466, -159.30176653939253, -154.27688897558514, -152.0854103810786, -145.75652847587313, -140.80843828908465, -139.23982133191495, -134.27073891256106, -132.12649284133064, -125.95947209775511, -121.00309550337462, -119.26736932643232, -114.2706655484383, -112.07393889578914, -105.72295990367157, -100.8088439880125, -99.2034906238494, -94.30042325164636, -92.15010048151461, -85.92203653534293, -81.03913275494665, -79.27412793784428, -74.33487658582118, -72.06274362408762, -65.76562628131825, -60.91255356825276, -59.20334389560392, -54.33286972659312, -52.19387171350535, -45.94978737932291, -41.03014719193582, -39.301602568238906, -34.35572209014114, -32.04464301272608, -25.8221033382824, -20.922074315528747, -19.21590299233186, -14.31090212502093, -12.217203140101386, -5.9283722049240435, -0.9863587170369246, 0.7408048387279834, 5.71126832601389, 7.972628957879352, 14.204559894256546, 19.11839959633025, 20.8218087836657, 25.678748486941828, 27.822718344586864, 34.062659474970715, 38.9745656819391, 40.77409719734158, 45.72080631619803, 47.974156754056835, 54.23453768983539, 59.12020360609568, 60.77306570712026, 65.70734521458867, 67.8344660434617, 74.03187028154134, 78.96532114824849, 80.76070945985495, 85.74802197591286, 87.9140889204674, 94.18082276873524, 99.25790470037091, 100.68454787413205, 105.7213026221542, 107.79483801526698, 113.99555681638138, 119.0707052529143, 120.72715813056156, 125.77551384921307, 127.91257836719551, 134.2011330887875, 139.23043006997628, 140.71673537840158, 145.76288138835983, 147.80216629676042, 154.06420451405637, 159.0846626604798, 160.76183155710717, 165.73699067536242, 167.9265357747636, 173.96705069576544, 178.2522282751915, 179.9042617354548, 183.54586165856657, 185.23269803071796, 189.41678143751972, 191.87149157986588, 192.8741468985015, 195.0241934550453, 195.966634211846, 197.9821647518146, 198.99006812859284, 199.33202054855676, 199.91897441965887, 200.11536227958896, 200.22280936469997, 200.25181179127208],'d') + y=to_array([11.0, 6.0, 8.0, 5.0, 11.0, 7.0, 18.0, 11.0, 12.0, 10.0, 8.0, 6.0, 16.0, 4.0, 12.0, 9.0, 15.0, 14.0, 8.0, 20.0, 15.0, 8.0, 9.0, 11.0, 13.0, 12.0, 13.0, 15.0, 13.0, 20.0, 10.0, 7.0, 17.0, 11.0, 20.0, 13.0, 13.0, 23.0, 14.0, 10.0, 17.0, 15.0, 20.0, 16.0, 14.0, 13.0, 18.0, 22.0, 9.0, 20.0, 12.0, 14.0, 17.0, 19.0, 14.0, 14.0, 23.0, 19.0, 15.0, 20.0, 20.0, 21.0, 20.0, 23.0, 22.0, 15.0, 10.0, 17.0, 21.0, 15.0, 23.0, 23.0, 25.0, 18.0, 16.0, 21.0, 22.0, 16.0, 16.0, 14.0, 19.0, 20.0, 18.0, 20.0, 23.0, 13.0, 16.0, 20.0, 25.0, 15.0, 15.0, 17.0, 22.0, 26.0, 19.0, 30.0, 25.0, 17.0, 17.0, 23.0, 16.0, 27.0, 21.0, 21.0, 26.0, 27.0, 21.0, 17.0, 20.0, 20.0, 21.0, 19.0, 25.0, 19.0, 13.0, 23.0, 20.0, 20.0, 18.0, 20.0, 19.0, 25.0],'d') + [off, amp, com, sigma] = gfitoff(x, y, off=None, amp=None, com=None, sigma=None) + #print "Fit: ", [off, amp, com, sigma] + g = Gaussian(amp, com, sigma) + plot([y, [g.value(i)+off for i in x]], ["data", "fit"], xdata = x, title=str(index)) + except: + traceback.print_exc() +task(0) +for i in range(3): + fork((task,(i+1,))) \ No newline at end of file diff --git a/script/test/TestPipelineStream.py b/script/test/TestPipelineStream.py new file mode 100644 index 0000000..8004ee3 --- /dev/null +++ b/script/test/TestPipelineStream.py @@ -0,0 +1,32 @@ +add_device(PipelineStream("p","localhost:8889", "simulation_sp"), True) +set_exec_pars(save=False) +#tscan(p.channels,10,0.1) + + +#Sync with stopped stream +p.update() #First update to create children devices +x_profile= p.getChannel("x_profile") +tscan((p,x_profile),3,0.1) + + + +#Async on running stream +p.monitored=True +p.stream.waitValueChange(-1) + + +class StreamImage(ReadableMatrix): + def read(self): + return Convert.reshape(p.getValue("image") , (self.getHeight(), self.getWidth())) + + def getWidth(self): + return p.getValue("width") + + def getHeight(self): + return p.getValue("height") + +mscan(p, StreamImage(), 5) + + +#Sync on running stream +tscan(p.channels,5,0.5) \ No newline at end of file diff --git a/script/test/TestSnapArray.py b/script/test/TestSnapArray.py new file mode 100644 index 0000000..3602282 --- /dev/null +++ b/script/test/TestSnapArray.py @@ -0,0 +1,8 @@ + +DIAGS = [wf1,im1] +SNAPS = [wf1,im1] + +ret = lscan(m1, ai1, 0.0, 1.0, 4, diags=DIAGS, snaps=SNAPS) + +plot(ret.getSnap(wf1)) +plot(ret.getDiag(im1)) diff --git a/script/test/TestSpectrum.py b/script/test/TestSpectrum.py new file mode 100644 index 0000000..229e20f --- /dev/null +++ b/script/test/TestSpectrum.py @@ -0,0 +1,149 @@ +#If running from editor +if (get_exec_pars().source == CommandSource.ui): + ROI = {"Region1": [10,5,20, 10], "Region2": [60,60,40, 40]} + SAVE_SPECTRUM = True + SAVE_IMAGES = True + WITH_I0 = False + SWITCH_POL = False + EXPOSURE = 1.0 + AVERAGE = 2 + NUMBER_SCANS =1 + RANGES = [[500.0, 1000.0, 100.0]] + +print "Starting AbsSpec: ",SAVE_SPECTRUM, SAVE_IMAGES, WITH_I0, SWITCH_POL, EXPOSURE, AVERAGE, NUMBER_SCANS, RANGES + +run("eiger") +machine_cur=sin +Keithley_1_raw = cv +def get_dry_run(): + return True +def set_outliers_threshold(value): + set_setting("OUTLIERS_THRESHOLD", int(value)) +def get_outliers_threshold(): + try: + return int(get_setting("OUTLIERS_THRESHOLD")) + except: + return 0 +def get_outliers_mask_file(): + return get_setting("OUTLIERS_MASK_FILE") + +if SWITCH_POL: + init_pol_switch("Normal") + + +log("ROIs: " + str(ROI)) + +if len(RANGES) == 0: + raise Exception("No scan range defined") + +start = time.time() + +init_eiger(exposure=EXPOSURE) + +set_exec_pars(name="AbsortionSpectrum", format="csv", layout="table") +tag= "AbsortionSpectrum_{seq}%03d_{count}%02d" + +rois = create_roi_devices(ROI) + +if AVERAGE>1: + for i in range(len(rois)): + rois[i].monitored = True + rois[i] = create_averager(rois[i], AVERAGE, -1) + rois[i].monitored = True + + +frames=[] + + +class SyncEnergy(Writable): + def write(self,pos): + if not get_dry_run(): + put_energy(pos) + else: + print "Energy=" + str(pos) +sync_energy=SyncEnergy() +set_device_alias(sync_energy, "Energy") #Set display name + +sensors = rois + [machine_cur] +if WITH_I0: + sensors = sensors + [Keithley_1_raw] + + +#### +def __save_as_tiff(index, data, filename,metadata={}): + if type(data) == Data: + ip = load_array(data.matrix) + else: + ip = data + + metadata["Timestamp"] = time.strftime("%y/%m/%d %H:%M:%S",time.localtime()) + if not os.path.exists(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + save_image(ip, filename,"tiff", metadata) + + d = get_ip_array(ip) + import java.util.Arrays as Arrays + ip=open_image(filename) + read = get_ip_array(ip) + if not Arrays.deepEquals(read, d): + print "Error reading array" + print index, filename + index=0 + for name,r in ROI.items(): + s=0.0 + for i in range(r[2]): + for j in range(r[3]): + #s = s + d[r[0]+i][r[1]+j] + el=data.getElement(r[0]+i, r[1]+j, False) + s = s + (0 if (math.isnan(el)) else el) + roi = data.getRoi(Rectangle(r[0],r[1], r[2], r[3])) + + print name, s, rois[index].take(), roi.integrate(False) + index=index+1 + + +#### + + +av=None +def grab_image(position, scan): + global av + av = average_eiger_frames(AVERAGE, roi=None, wait_next=True) + + if SAVE_IMAGES: + filename = get_exec_pars().path + "/" + str(scan.currentPass) + "/s" + "{seq}%03d" + "_" + ("%03d.tif" % scan.recordIndex) + filename = get_context().setup.expandPath(filename) + #print filename + #save_as_tiff(av, filename, metadata={}) + #fork((__save_as_tiff,(scan.recordIndex, av, filename)),) + __save_as_tiff(scan.recordIndex, av, filename) +class Average(ReadableMatrix): + def read(self): + return av.matrix + def getWidth(self): + return image.getData().getWidth() + def getHeight(self): + return image.getData().getHeight() +averager=Average() +set_device_alias(averager, "Image") #Set display name +sensors.append(averager) + +#Initialize vartiables +if not get_dry_run() and str(get_setting("AUTO_SWITCH_VALVE")).lower() == "true": + open_vg10() + + +def after_pass(pass_number, scan): + if SWITCH_POL: + if pass_number1: + for i in range(len(rois)): + rois[i].parent.monitored = False # Remove listeners on the image + if not get_dry_run() and str(get_setting("AUTO_SWITCH_VALVE")).lower() == "true": + close_vg10() + restore_eiger() + print "Running time: " + str(time.time() - start) \ No newline at end of file diff --git a/script/test/TestStreamDemo.py b/script/test/TestStreamDemo.py new file mode 100644 index 0000000..542531a --- /dev/null +++ b/script/test/TestStreamDemo.py @@ -0,0 +1,15 @@ +STREAM_URL="tcp://localhost:5552" + + + +st1=Stream("st1", STREAM_URL, SocketType.SUB) +st1.createMatrix = True +st1.initialize() +st1.start() +st1.waitCacheChange(2000) + + +show_panel(st1) + + +#bscan(st1, 10, -1) \ No newline at end of file diff --git a/script/test/TestStreamNotFixed.py b/script/test/TestStreamNotFixed.py index 47d8e76..392454c 100644 --- a/script/test/TestStreamNotFixed.py +++ b/script/test/TestStreamNotFixed.py @@ -2,22 +2,33 @@ import ch.psi.pshell.bs.Provider as Provider url="tcp://localhost:9999" url="tcp://SFTEST-CVME-DBPM1:9001" #url= "tcp://SIN-CVME-DBPM0421:9000" -p=Provider("provider", url, True) -p.config.keepListeningOnStop = True -p.config.parallelHandlerProcessing = False -p.config.byteBufferAllocator = True -st1 = Stream("st1", p) +url="tcp://localhost:5553" +#url="tcp://localhost:10101" +#p=Provider("provider", url, False) +#p.config.keepListeningOnStop = True +#p.config.parallelHandlerProcessing = False +#p.config.byteBufferAllocator = True +#st1 = + +st1=Stream("st1", url, SocketType.SUB) +#st1.addScalar("scalar") +#st1.addMatrix("img") +#st1.addScalar("str") +#st1.addWaveform("waveform") + st1.initialize() st1.start() -st1.waitCacheChange(20000) +st1.waitCacheChange(2000) - -#try: -# bscan (st1, 5, 5, save=False) -#finally: -# st1.close() +""" +try: + bscan (st1, 5, 5, save=False) +finally: + st1.close() try: print st1.getValues() finally: st1.close() + +""" diff --git a/script/test/TestStrip.py b/script/test/TestStrip.py new file mode 100644 index 0000000..4956aab --- /dev/null +++ b/script/test/TestStrip.py @@ -0,0 +1,21 @@ +import random + +#################################################################################################### +# Simulated Devices +#################################################################################################### + +class AnalogInput(ReadonlyRegisterBase): + def doRead(self): + time.sleep(0.001) + self.val = to_array(self.calc(), 'd') + return self.val + +class Random(AnalogInput): + def calc(self): + return random.random() + + +for i in range(20): + r=Random("r"+str(i)) + add_device(r, True) + r.polling=5 diff --git a/script/test/TestTablePlot.py b/script/test/TestTablePlot.py new file mode 100644 index 0000000..7fb2d3d --- /dev/null +++ b/script/test/TestTablePlot.py @@ -0,0 +1,2 @@ +CUSTOM_PLOT_TYPES = {scienta.dataMatrix:"ch.psi.pshell.plot.TablePlot"} +tscan(scienta.dataMatrix, 5, 0.1, save=False, plot_types=CUSTOM_PLOT_TYPES) \ No newline at end of file diff --git a/script/test/TestTerminal.py b/script/test/TestTerminal.py new file mode 100644 index 0000000..7a07fb0 --- /dev/null +++ b/script/test/TestTerminal.py @@ -0,0 +1,24 @@ +import ch.psi.utils.swing.Terminal as Terminal +import ch.psi.utils.swing.SwingUtils as SwingUtils +terminal=None +view=App.getInstance().getMainFrame() +tabStatus=view.getStatusTab() + +def showTerminal(): + global terminal + terminal = Terminal(".", 13) + tabStatus.addTab("T", terminal) + index = tabStatus.getTabCount() - 1 + SwingUtils.setTabClosable(tabStatus, index) + tabStatus.setSelectedComponent(terminal) + +def hideTerminal(): + global terminal + if terminal is not None: + tabStatus.remove(terminal) + terminal = None + +for i in range (100): + showTerminal() + hideTerminal() + \ No newline at end of file diff --git a/script/test/TestVscan.py b/script/test/TestVscan.py new file mode 100644 index 0000000..268e80b --- /dev/null +++ b/script/test/TestVscan.py @@ -0,0 +1,2 @@ +vector = [ 1, 3, 5, 10, 25, 40, 45, 47, 49] +r1 = vscan(motor,(sin),vector,False, 0.5, relative=True,title = "1D Vector", before_read=None, after_read=None) \ No newline at end of file diff --git a/script/test/TstImgSim.py b/script/test/TstImgSim.py new file mode 100644 index 0000000..afbd596 --- /dev/null +++ b/script/test/TstImgSim.py @@ -0,0 +1,43 @@ +run ("imaging/sim") + +stack = load_test_stack(show=True) +ipr, ipi = complex_edge_filtering(stack, complex=True, g_sigma = 3.0, show=False) + + +#complex_edge_filter = get_context().getClassByName("Align_ComplexEdgeFiltering").newInstance() +#translation_filter = get_context().getClassByName("Align_TranslationFilter2").newInstance() +#compute_shifts_filter = get_context().getClassByName("Align_ComputeShifts2").newInstance() + +""" +ip_list = [] +for index in range(40,49): + ip_list.append(open_image(expand_path("{images}/TestObjAligner/i210517_0" + str(index) + "#001.tif"))) +stack = create_stack(ip_list, title = "Test") +stack.show() + +complex_edge_filter.setup("2,True,False", stack) #Gaussian blur radius, Complex (True) or Real (False), show dialog = False +complex_edge_filter.r"un(stack.getProcessor()) +imp_r, imp_i = complex_edge_filter.output + +#imp_r.show() +#imp_i.show() +roi=Roi(258,0,256,256) +compute_shifts_filter.setup(10, False, ipr, ipi, 1, roi) +compute_shifts_filter.run(None) +shifts = compute_shifts_filter.shifts + +translation_filter.shifts = shifts +translation_filter.setup("",stack) +translation_filter.run(stack.getProcessor()) + +translation_filter.registred.show(); +translation_filter.registred.updateAndDraw(); + +""" + +roi=Roi(258,0,256,256) +shifts = calculate_shifts(ipr, ipi, roi, 2) +r=translate(stack, shifts, True) + + + diff --git a/script/test/TstImgSim2.py b/script/test/TstImgSim2.py new file mode 100644 index 0000000..92676d8 --- /dev/null +++ b/script/test/TstImgSim2.py @@ -0,0 +1,44 @@ +from ijutils import Roi +#from sim import * +import traceback +run ("imaging/sim") + +roi=Roi(0,0,128,128) + +""" +stack = load_test_stack(show=True, size=2) +ipr, ipi = complex_edge_filtering(stack, show=False) +shifts = calculate_shifts(ipr, ipi, roi, java_code=True) +#shifts= load_shifts("{images}/TestObjAligner/shifts.mat") +#stack = load_test_stack(show=True) +r=translate(stack, shifts, show=True) +""" + + + + +ref = "{images}/TestObjAligner/i210517_040#001.tif" +img = "{images}/TestObjAligner/i210517_041#001.tif" + +ref = open_image(ref) +img = open_image(img) +#ref=ref.getBufferedImage() +#img=img.getBufferedImage() +#ref=ca.data +img=grayscale(to_ip(ca.data)) +ref = grayscale(ref.crop([Roi(0,0,452,452)])[0]) + +def calculate_shift(ref,img, roi, upscale_factor=100, reference_slide=1): + ref = to_ip(ref) + img = to_ip(img) + stack = create_stack([ref,img]) + stack.show() + ipr, ipi = complex_edge_filtering(stack, show=False) + shifts = calculate_shifts(ipr, ipi, roi, java_code=True) + xoff, yoff = shifts[1][3], shifts[1][2] + error, diffphase = shifts[1][4], shifts[1][5] + r=translate(stack, shifts, show=True) + return xoff, yoff,error, diffphase + + +print calculate_shift(ref,img, roi) diff --git a/script/test/VERSI_BEAMSTOP_SCAN.xml b/script/test/VERSI_BEAMSTOP_SCAN.xml new file mode 100644 index 0000000..5e483e7 --- /dev/null +++ b/script/test/VERSI_BEAMSTOP_SCAN.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + -2.0 + 2.0 + 0.1 + 0.2 + 0.25 + + + + + + + + + + + 0.0 + 2.0 + 0.1 + X_INIT + X_FINAL + + + + + + + + + + + + + + diff --git a/script/test/calc.py b/script/test/calc.py index 7d85119..988c123 100755 --- a/script/test/calc.py +++ b/script/test/calc.py @@ -1,3 +1,4 @@ def calc(a): return a*4 +print calc(10) \ No newline at end of file diff --git a/script/test/inspect.py b/script/test/inspect.py new file mode 100644 index 0000000..adf0653 --- /dev/null +++ b/script/test/inspect.py @@ -0,0 +1,3128 @@ +"""Get useful information from live Python objects. + +This module encapsulates the interface provided by the internal special +attributes (co_*, im_*, tb_*, etc.) in a friendlier fashion. +It also provides some help for examining source code and class layout. + +Here are some of the useful functions provided by this module: + + ismodule(), isclass(), ismethod(), isfunction(), isgeneratorfunction(), + isgenerator(), istraceback(), isframe(), iscode(), isbuiltin(), + isroutine() - check object types + getmembers() - get members of an object that satisfy a given condition + + getfile(), getsourcefile(), getsource() - find an object's source code + getdoc(), getcomments() - get documentation on an object + getmodule() - determine the module that an object came from + getclasstree() - arrange classes so as to represent their hierarchy + + getargvalues(), getcallargs() - get info about function arguments + getfullargspec() - same, with support for Python 3 features + formatargspec(), formatargvalues() - format an argument spec + getouterframes(), getinnerframes() - get info about frames + currentframe() - get the current stack frame + stack(), trace() - get info about frames on the stack or in a traceback + + signature() - get a Signature object for the callable +""" + +# This module is in the public domain. No warranties. + +__author__ = ('Ka-Ping Yee ', + 'Yury Selivanov ') + +import abc +import ast +import dis +import collections.abc +import enum +import importlib.machinery +import itertools +import linecache +import os +import re +import sys +import tokenize +import token +import types +import warnings +import functools +import builtins +from operator import attrgetter +from collections import namedtuple, OrderedDict + +# Create constants for the compiler flags in Include/code.h +# We try to get them from dis to avoid duplication +mod_dict = globals() +for k, v in dis.COMPILER_FLAG_NAMES.items(): + mod_dict["CO_" + v] = k + +# See Include/object.h +TPFLAGS_IS_ABSTRACT = 1 << 20 + +# ----------------------------------------------------------- type-checking +def ismodule(object): + """Return true if the object is a module. + + Module objects provide these attributes: + __cached__ pathname to byte compiled file + __doc__ documentation string + __file__ filename (missing for built-in modules)""" + return isinstance(object, types.ModuleType) + +def isclass(object): + """Return true if the object is a class. + + Class objects provide these attributes: + __doc__ documentation string + __module__ name of module in which this class was defined""" + return isinstance(object, type) + +def ismethod(object): + """Return true if the object is an instance method. + + Instance method objects provide these attributes: + __doc__ documentation string + __name__ name with which this method was defined + __func__ function object containing implementation of method + __self__ instance to which this method is bound""" + return isinstance(object, types.MethodType) + +def ismethoddescriptor(object): + """Return true if the object is a method descriptor. + + But not if ismethod() or isclass() or isfunction() are true. + + This is new in Python 2.2, and, for example, is true of int.__add__. + An object passing this test has a __get__ attribute but not a __set__ + attribute, but beyond that the set of attributes varies. __name__ is + usually sensible, and __doc__ often is. + + Methods implemented via descriptors that also pass one of the other + tests return false from the ismethoddescriptor() test, simply because + the other tests promise more -- you can, e.g., count on having the + __func__ attribute (etc) when an object passes ismethod().""" + if isclass(object) or ismethod(object) or isfunction(object): + # mutual exclusion + return False + tp = type(object) + return hasattr(tp, "__get__") and not hasattr(tp, "__set__") + +def isdatadescriptor(object): + """Return true if the object is a data descriptor. + + Data descriptors have both a __get__ and a __set__ attribute. Examples are + properties (defined in Python) and getsets and members (defined in C). + Typically, data descriptors will also have __name__ and __doc__ attributes + (properties, getsets, and members have both of these attributes), but this + is not guaranteed.""" + if isclass(object) or ismethod(object) or isfunction(object): + # mutual exclusion + return False + tp = type(object) + return hasattr(tp, "__set__") and hasattr(tp, "__get__") + +if hasattr(types, 'MemberDescriptorType'): + # CPython and equivalent + def ismemberdescriptor(object): + """Return true if the object is a member descriptor. + + Member descriptors are specialized descriptors defined in extension + modules.""" + return isinstance(object, types.MemberDescriptorType) +else: + # Other implementations + def ismemberdescriptor(object): + """Return true if the object is a member descriptor. + + Member descriptors are specialized descriptors defined in extension + modules.""" + return False + +if hasattr(types, 'GetSetDescriptorType'): + # CPython and equivalent + def isgetsetdescriptor(object): + """Return true if the object is a getset descriptor. + + getset descriptors are specialized descriptors defined in extension + modules.""" + return isinstance(object, types.GetSetDescriptorType) +else: + # Other implementations + def isgetsetdescriptor(object): + """Return true if the object is a getset descriptor. + + getset descriptors are specialized descriptors defined in extension + modules.""" + return False + +def isfunction(object): + """Return true if the object is a user-defined function. + + Function objects provide these attributes: + __doc__ documentation string + __name__ name with which this function was defined + __code__ code object containing compiled function bytecode + __defaults__ tuple of any default values for arguments + __globals__ global namespace in which this function was defined + __annotations__ dict of parameter annotations + __kwdefaults__ dict of keyword only parameters with defaults""" + return isinstance(object, types.FunctionType) + +def isgeneratorfunction(object): + """Return true if the object is a user-defined generator function. + + Generator function objects provide the same attributes as functions. + See help(isfunction) for a list of attributes.""" + return bool((isfunction(object) or ismethod(object)) and + object.__code__.co_flags & CO_GENERATOR) + +def iscoroutinefunction(object): + """Return true if the object is a coroutine function. + + Coroutine functions are defined with "async def" syntax. + """ + return bool((isfunction(object) or ismethod(object)) and + object.__code__.co_flags & CO_COROUTINE) + +def isasyncgenfunction(object): + """Return true if the object is an asynchronous generator function. + + Asynchronous generator functions are defined with "async def" + syntax and have "yield" expressions in their body. + """ + return bool((isfunction(object) or ismethod(object)) and + object.__code__.co_flags & CO_ASYNC_GENERATOR) + +def isasyncgen(object): + """Return true if the object is an asynchronous generator.""" + return isinstance(object, types.AsyncGeneratorType) + +def isgenerator(object): + """Return true if the object is a generator. + + Generator objects provide these attributes: + __iter__ defined to support iteration over container + close raises a new GeneratorExit exception inside the + generator to terminate the iteration + gi_code code object + gi_frame frame object or possibly None once the generator has + been exhausted + gi_running set to 1 when generator is executing, 0 otherwise + next return the next item from the container + send resumes the generator and "sends" a value that becomes + the result of the current yield-expression + throw used to raise an exception inside the generator""" + return isinstance(object, types.GeneratorType) + +def iscoroutine(object): + """Return true if the object is a coroutine.""" + return isinstance(object, types.CoroutineType) + +def isawaitable(object): + """Return true if object can be passed to an ``await`` expression.""" + return (isinstance(object, types.CoroutineType) or + isinstance(object, types.GeneratorType) and + bool(object.gi_code.co_flags & CO_ITERABLE_COROUTINE) or + isinstance(object, collections.abc.Awaitable)) + +def istraceback(object): + """Return true if the object is a traceback. + + Traceback objects provide these attributes: + tb_frame frame object at this level + tb_lasti index of last attempted instruction in bytecode + tb_lineno current line number in Python source code + tb_next next inner traceback object (called by this level)""" + return isinstance(object, types.TracebackType) + +def isframe(object): + """Return true if the object is a frame object. + + Frame objects provide these attributes: + f_back next outer frame object (this frame's caller) + f_builtins built-in namespace seen by this frame + f_code code object being executed in this frame + f_globals global namespace seen by this frame + f_lasti index of last attempted instruction in bytecode + f_lineno current line number in Python source code + f_locals local namespace seen by this frame + f_trace tracing function for this frame, or None""" + return isinstance(object, types.FrameType) + +def iscode(object): + """Return true if the object is a code object. + + Code objects provide these attributes: + co_argcount number of arguments (not including *, ** args + or keyword only arguments) + co_code string of raw compiled bytecode + co_cellvars tuple of names of cell variables + co_consts tuple of constants used in the bytecode + co_filename name of file in which this code object was created + co_firstlineno number of first line in Python source code + co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg + | 16=nested | 32=generator | 64=nofree | 128=coroutine + | 256=iterable_coroutine | 512=async_generator + co_freevars tuple of names of free variables + co_kwonlyargcount number of keyword only arguments (not including ** arg) + co_lnotab encoded mapping of line numbers to bytecode indices + co_name name with which this code object was defined + co_names tuple of names of local variables + co_nlocals number of local variables + co_stacksize virtual machine stack space required + co_varnames tuple of names of arguments and local variables""" + return isinstance(object, types.CodeType) + +def isbuiltin(object): + """Return true if the object is a built-in function or method. + + Built-in functions and methods provide these attributes: + __doc__ documentation string + __name__ original name of this function or method + __self__ instance to which a method is bound, or None""" + return isinstance(object, types.BuiltinFunctionType) + +def isroutine(object): + """Return true if the object is any kind of function or method.""" + return (isbuiltin(object) + or isfunction(object) + or ismethod(object) + or ismethoddescriptor(object)) + +def isabstract(object): + """Return true if the object is an abstract base class (ABC).""" + if not isinstance(object, type): + return False + if object.__flags__ & TPFLAGS_IS_ABSTRACT: + return True + if not issubclass(type(object), abc.ABCMeta): + return False + if hasattr(object, '__abstractmethods__'): + # It looks like ABCMeta.__new__ has finished running; + # TPFLAGS_IS_ABSTRACT should have been accurate. + return False + # It looks like ABCMeta.__new__ has not finished running yet; we're + # probably in __init_subclass__. We'll look for abstractmethods manually. + for name, value in object.__dict__.items(): + if getattr(value, "__isabstractmethod__", False): + return True + for base in object.__bases__: + for name in getattr(base, "__abstractmethods__", ()): + value = getattr(object, name, None) + if getattr(value, "__isabstractmethod__", False): + return True + return False + +def getmembers(object, predicate=None): + """Return all members of an object as (name, value) pairs sorted by name. + Optionally, only return members that satisfy a given predicate.""" + if isclass(object): + mro = (object,) + getmro(object) + else: + mro = () + results = [] + processed = set() + names = dir(object) + # :dd any DynamicClassAttributes to the list of names if object is a class; + # this may result in duplicate entries if, for example, a virtual + # attribute with the same name as a DynamicClassAttribute exists + try: + for base in object.__bases__: + for k, v in base.__dict__.items(): + if isinstance(v, types.DynamicClassAttribute): + names.append(k) + except AttributeError: + pass + for key in names: + # First try to get the value via getattr. Some descriptors don't + # like calling their __get__ (see bug #1785), so fall back to + # looking in the __dict__. + try: + value = getattr(object, key) + # handle the duplicate key + if key in processed: + raise AttributeError + except AttributeError: + for base in mro: + if key in base.__dict__: + value = base.__dict__[key] + break + else: + # could be a (currently) missing slot member, or a buggy + # __dir__; discard and move on + continue + if not predicate or predicate(value): + results.append((key, value)) + processed.add(key) + results.sort(key=lambda pair: pair[0]) + return results + +Attribute = namedtuple('Attribute', 'name kind defining_class object') + +def classify_class_attrs(cls): + """Return list of attribute-descriptor tuples. + + For each name in dir(cls), the return list contains a 4-tuple + with these elements: + + 0. The name (a string). + + 1. The kind of attribute this is, one of these strings: + 'class method' created via classmethod() + 'static method' created via staticmethod() + 'property' created via property() + 'method' any other flavor of method or descriptor + 'data' not a method + + 2. The class which defined this attribute (a class). + + 3. The object as obtained by calling getattr; if this fails, or if the + resulting object does not live anywhere in the class' mro (including + metaclasses) then the object is looked up in the defining class's + dict (found by walking the mro). + + If one of the items in dir(cls) is stored in the metaclass it will now + be discovered and not have None be listed as the class in which it was + defined. Any items whose home class cannot be discovered are skipped. + """ + + mro = getmro(cls) + metamro = getmro(type(cls)) # for attributes stored in the metaclass + metamro = tuple([cls for cls in metamro if cls not in (type, object)]) + class_bases = (cls,) + mro + all_bases = class_bases + metamro + names = dir(cls) + # :dd any DynamicClassAttributes to the list of names; + # this may result in duplicate entries if, for example, a virtual + # attribute with the same name as a DynamicClassAttribute exists. + for base in mro: + for k, v in base.__dict__.items(): + if isinstance(v, types.DynamicClassAttribute): + names.append(k) + result = [] + processed = set() + + for name in names: + # Get the object associated with the name, and where it was defined. + # Normal objects will be looked up with both getattr and directly in + # its class' dict (in case getattr fails [bug #1785], and also to look + # for a docstring). + # For DynamicClassAttributes on the second pass we only look in the + # class's dict. + # + # Getting an obj from the __dict__ sometimes reveals more than + # using getattr. Static and class methods are dramatic examples. + homecls = None + get_obj = None + dict_obj = None + if name not in processed: + try: + if name == '__dict__': + raise Exception("__dict__ is special, don't want the proxy") + get_obj = getattr(cls, name) + except Exception as exc: + pass + else: + homecls = getattr(get_obj, "__objclass__", homecls) + if homecls not in class_bases: + # if the resulting object does not live somewhere in the + # mro, drop it and search the mro manually + homecls = None + last_cls = None + # first look in the classes + for srch_cls in class_bases: + srch_obj = getattr(srch_cls, name, None) + if srch_obj is get_obj: + last_cls = srch_cls + # then check the metaclasses + for srch_cls in metamro: + try: + srch_obj = srch_cls.__getattr__(cls, name) + except AttributeError: + continue + if srch_obj is get_obj: + last_cls = srch_cls + if last_cls is not None: + homecls = last_cls + for base in all_bases: + if name in base.__dict__: + dict_obj = base.__dict__[name] + if homecls not in metamro: + homecls = base + break + if homecls is None: + # unable to locate the attribute anywhere, most likely due to + # buggy custom __dir__; discard and move on + continue + obj = get_obj if get_obj is not None else dict_obj + # Classify the object or its descriptor. + if isinstance(dict_obj, staticmethod): + kind = "static method" + obj = dict_obj + elif isinstance(dict_obj, classmethod): + kind = "class method" + obj = dict_obj + elif isinstance(dict_obj, property): + kind = "property" + obj = dict_obj + elif isroutine(obj): + kind = "method" + else: + kind = "data" + result.append(Attribute(name, kind, homecls, obj)) + processed.add(name) + return result + +# ----------------------------------------------------------- class helpers + +def getmro(cls): + "Return tuple of base classes (including cls) in method resolution order." + return cls.__mro__ + +# -------------------------------------------------------- function helpers + +def unwrap(func, *, stop=None): + """Get the object wrapped by *func*. + + Follows the chain of :attr:`__wrapped__` attributes returning the last + object in the chain. + + *stop* is an optional callback accepting an object in the wrapper chain + as its sole argument that allows the unwrapping to be terminated early if + the callback returns a true value. If the callback never returns a true + value, the last object in the chain is returned as usual. For example, + :func:`signature` uses this to stop unwrapping if any object in the + chain has a ``__signature__`` attribute defined. + + :exc:`ValueError` is raised if a cycle is encountered. + + """ + if stop is None: + def _is_wrapper(f): + return hasattr(f, '__wrapped__') + else: + def _is_wrapper(f): + return hasattr(f, '__wrapped__') and not stop(f) + f = func # remember the original func for error reporting + # Memoise by id to tolerate non-hashable objects, but store objects to + # ensure they aren't destroyed, which would allow their IDs to be reused. + memo = {id(f): f} + recursion_limit = sys.getrecursionlimit() + while _is_wrapper(func): + func = func.__wrapped__ + id_func = id(func) + if (id_func in memo) or (len(memo) >= recursion_limit): + raise ValueError('wrapper loop when unwrapping {!r}'.format(f)) + memo[id_func] = func + return func + +# -------------------------------------------------- source code extraction +def indentsize(line): + """Return the indent size, in spaces, at the start of a line of text.""" + expline = line.expandtabs() + return len(expline) - len(expline.lstrip()) + +def _findclass(func): + cls = sys.modules.get(func.__module__) + if cls is None: + return None + for name in func.__qualname__.split('.')[:-1]: + cls = getattr(cls, name) + if not isclass(cls): + return None + return cls + +def _finddoc(obj): + if isclass(obj): + for base in obj.__mro__: + if base is not object: + try: + doc = base.__doc__ + except AttributeError: + continue + if doc is not None: + return doc + return None + + if ismethod(obj): + name = obj.__func__.__name__ + self = obj.__self__ + if (isclass(self) and + getattr(getattr(self, name, None), '__func__') is obj.__func__): + # classmethod + cls = self + else: + cls = self.__class__ + elif isfunction(obj): + name = obj.__name__ + cls = _findclass(obj) + if cls is None or getattr(cls, name) is not obj: + return None + elif isbuiltin(obj): + name = obj.__name__ + self = obj.__self__ + if (isclass(self) and + self.__qualname__ + '.' + name == obj.__qualname__): + # classmethod + cls = self + else: + cls = self.__class__ + # Should be tested before isdatadescriptor(). + elif isinstance(obj, property): + func = obj.fget + name = func.__name__ + cls = _findclass(func) + if cls is None or getattr(cls, name) is not obj: + return None + elif ismethoddescriptor(obj) or isdatadescriptor(obj): + name = obj.__name__ + cls = obj.__objclass__ + if getattr(cls, name) is not obj: + return None + else: + return None + + for base in cls.__mro__: + try: + doc = getattr(base, name).__doc__ + except AttributeError: + continue + if doc is not None: + return doc + return None + +def getdoc(object): + """Get the documentation string for an object. + + All tabs are expanded to spaces. To clean up docstrings that are + indented to line up with blocks of code, any whitespace than can be + uniformly removed from the second line onwards is removed.""" + try: + doc = object.__doc__ + except AttributeError: + return None + if doc is None: + try: + doc = _finddoc(object) + except (AttributeError, TypeError): + return None + if not isinstance(doc, str): + return None + return cleandoc(doc) + +def cleandoc(doc): + """Clean up indentation from docstrings. + + Any whitespace that can be uniformly removed from the second line + onwards is removed.""" + try: + lines = doc.expandtabs().split('\n') + except UnicodeError: + return None + else: + # Find minimum indentation of any non-blank lines after first line. + margin = sys.maxsize + for line in lines[1:]: + content = len(line.lstrip()) + if content: + indent = len(line) - content + margin = min(margin, indent) + # Remove indentation. + if lines: + lines[0] = lines[0].lstrip() + if margin < sys.maxsize: + for i in range(1, len(lines)): lines[i] = lines[i][margin:] + # Remove any trailing or leading blank lines. + while lines and not lines[-1]: + lines.pop() + while lines and not lines[0]: + lines.pop(0) + return '\n'.join(lines) + +def getfile(object): + """Work out which source or compiled file an object was defined in.""" + if ismodule(object): + if hasattr(object, '__file__'): + return object.__file__ + raise TypeError('{!r} is a built-in module'.format(object)) + if isclass(object): + if hasattr(object, '__module__'): + object = sys.modules.get(object.__module__) + if hasattr(object, '__file__'): + return object.__file__ + raise TypeError('{!r} is a built-in class'.format(object)) + if ismethod(object): + object = object.__func__ + if isfunction(object): + object = object.__code__ + if istraceback(object): + object = object.tb_frame + if isframe(object): + object = object.f_code + if iscode(object): + return object.co_filename + raise TypeError('{!r} is not a module, class, method, ' + 'function, traceback, frame, or code object'.format(object)) + +def getmodulename(path): + """Return the module name for a given file, or None.""" + fname = os.path.basename(path) + # Check for paths that look like an actual module file + suffixes = [(-len(suffix), suffix) + for suffix in importlib.machinery.all_suffixes()] + suffixes.sort() # try longest suffixes first, in case they overlap + for neglen, suffix in suffixes: + if fname.endswith(suffix): + return fname[:neglen] + return None + +def getsourcefile(object): + """Return the filename that can be used to locate an object's source. + Return None if no way can be identified to get the source. + """ + filename = getfile(object) + all_bytecode_suffixes = importlib.machinery.DEBUG_BYTECODE_SUFFIXES[:] + all_bytecode_suffixes += importlib.machinery.OPTIMIZED_BYTECODE_SUFFIXES[:] + if any(filename.endswith(s) for s in all_bytecode_suffixes): + filename = (os.path.splitext(filename)[0] + + importlib.machinery.SOURCE_SUFFIXES[0]) + elif any(filename.endswith(s) for s in + importlib.machinery.EXTENSION_SUFFIXES): + return None + if os.path.exists(filename): + return filename + # only return a non-existent filename if the module has a PEP 302 loader + if getattr(getmodule(object, filename), '__loader__', None) is not None: + return filename + # or it is in the linecache + if filename in linecache.cache: + return filename + +def getabsfile(object, _filename=None): + """Return an absolute path to the source or compiled file for an object. + + The idea is for each object to have a unique origin, so this routine + normalizes the result as much as possible.""" + if _filename is None: + _filename = getsourcefile(object) or getfile(object) + return os.path.normcase(os.path.abspath(_filename)) + +modulesbyfile = {} +_filesbymodname = {} + +def getmodule(object, _filename=None): + """Return the module an object was defined in, or None if not found.""" + if ismodule(object): + return object + if hasattr(object, '__module__'): + return sys.modules.get(object.__module__) + # Try the filename to modulename cache + if _filename is not None and _filename in modulesbyfile: + return sys.modules.get(modulesbyfile[_filename]) + # Try the cache again with the absolute file name + try: + file = getabsfile(object, _filename) + except TypeError: + return None + if file in modulesbyfile: + return sys.modules.get(modulesbyfile[file]) + # Update the filename to module name cache and check yet again + # Copy sys.modules in order to cope with changes while iterating + for modname, module in list(sys.modules.items()): + if ismodule(module) and hasattr(module, '__file__'): + f = module.__file__ + if f == _filesbymodname.get(modname, None): + # Have already mapped this module, so skip it + continue + _filesbymodname[modname] = f + f = getabsfile(module) + # Always map to the name the module knows itself by + modulesbyfile[f] = modulesbyfile[ + os.path.realpath(f)] = module.__name__ + if file in modulesbyfile: + return sys.modules.get(modulesbyfile[file]) + # Check the main module + main = sys.modules['__main__'] + if not hasattr(object, '__name__'): + return None + if hasattr(main, object.__name__): + mainobject = getattr(main, object.__name__) + if mainobject is object: + return main + # Check builtins + builtin = sys.modules['builtins'] + if hasattr(builtin, object.__name__): + builtinobject = getattr(builtin, object.__name__) + if builtinobject is object: + return builtin + +def findsource(object): + """Return the entire source file and starting line number for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a list of all the lines + in the file and the line number indexes a line in that list. An OSError + is raised if the source code cannot be retrieved.""" + + file = getsourcefile(object) + if file: + # Invalidate cache if needed. + linecache.checkcache(file) + else: + file = getfile(object) + # Allow filenames in form of "" to pass through. + # `doctest` monkeypatches `linecache` module to enable + # inspection, so let `linecache.getlines` to be called. + if not (file.startswith('<') and file.endswith('>')): + raise OSError('source code not available') + + module = getmodule(object, file) + if module: + lines = linecache.getlines(file, module.__dict__) + else: + lines = linecache.getlines(file) + if not lines: + raise OSError('could not get source code') + + if ismodule(object): + return lines, 0 + + if isclass(object): + name = object.__name__ + pat = re.compile(r'^(\s*)class\s*' + name + r'\b') + # make some effort to find the best matching class definition: + # use the one with the least indentation, which is the one + # that's most probably not inside a function definition. + candidates = [] + for i in range(len(lines)): + match = pat.match(lines[i]) + if match: + # if it's at toplevel, it's already the best one + if lines[i][0] == 'c': + return lines, i + # else add whitespace to candidate list + candidates.append((match.group(1), i)) + if candidates: + # this will sort by whitespace, and by line number, + # less whitespace first + candidates.sort() + return lines, candidates[0][1] + else: + raise OSError('could not find class definition') + + if ismethod(object): + object = object.__func__ + if isfunction(object): + object = object.__code__ + if istraceback(object): + object = object.tb_frame + if isframe(object): + object = object.f_code + if iscode(object): + if not hasattr(object, 'co_firstlineno'): + raise OSError('could not find function definition') + lnum = object.co_firstlineno - 1 + pat = re.compile(r'^(\s*def\s)|(\s*async\s+def\s)|(.*(? 0: + if pat.match(lines[lnum]): break + lnum = lnum - 1 + return lines, lnum + raise OSError('could not find code object') + +def getcomments(object): + """Get lines of comments immediately preceding an object's source code. + + Returns None when source can't be found. + """ + try: + lines, lnum = findsource(object) + except (OSError, TypeError): + return None + + if ismodule(object): + # Look for a comment block at the top of the file. + start = 0 + if lines and lines[0][:2] == '#!': start = 1 + while start < len(lines) and lines[start].strip() in ('', '#'): + start = start + 1 + if start < len(lines) and lines[start][:1] == '#': + comments = [] + end = start + while end < len(lines) and lines[end][:1] == '#': + comments.append(lines[end].expandtabs()) + end = end + 1 + return ''.join(comments) + + # Look for a preceding block of comments at the same indentation. + elif lnum > 0: + indent = indentsize(lines[lnum]) + end = lnum - 1 + if end >= 0 and lines[end].lstrip()[:1] == '#' and \ + indentsize(lines[end]) == indent: + comments = [lines[end].expandtabs().lstrip()] + if end > 0: + end = end - 1 + comment = lines[end].expandtabs().lstrip() + while comment[:1] == '#' and indentsize(lines[end]) == indent: + comments[:0] = [comment] + end = end - 1 + if end < 0: break + comment = lines[end].expandtabs().lstrip() + while comments and comments[0].strip() == '#': + comments[:1] = [] + while comments and comments[-1].strip() == '#': + comments[-1:] = [] + return ''.join(comments) + +class EndOfBlock(Exception): pass + +class BlockFinder: + """Provide a tokeneater() method to detect the end of a code block.""" + def __init__(self): + self.indent = 0 + self.islambda = False + self.started = False + self.passline = False + self.indecorator = False + self.decoratorhasargs = False + self.last = 1 + + def tokeneater(self, type, token, srowcol, erowcol, line): + if not self.started and not self.indecorator: + # skip any decorators + if token == "@": + self.indecorator = True + # look for the first "def", "class" or "lambda" + elif token in ("def", "class", "lambda"): + if token == "lambda": + self.islambda = True + self.started = True + self.passline = True # skip to the end of the line + elif token == "(": + if self.indecorator: + self.decoratorhasargs = True + elif token == ")": + if self.indecorator: + self.indecorator = False + self.decoratorhasargs = False + elif type == tokenize.NEWLINE: + self.passline = False # stop skipping when a NEWLINE is seen + self.last = srowcol[0] + if self.islambda: # lambdas always end at the first NEWLINE + raise EndOfBlock + # hitting a NEWLINE when in a decorator without args + # ends the decorator + if self.indecorator and not self.decoratorhasargs: + self.indecorator = False + elif self.passline: + pass + elif type == tokenize.INDENT: + self.indent = self.indent + 1 + self.passline = True + elif type == tokenize.DEDENT: + self.indent = self.indent - 1 + # the end of matching indent/dedent pairs end a block + # (note that this only works for "def"/"class" blocks, + # not e.g. for "if: else:" or "try: finally:" blocks) + if self.indent <= 0: + raise EndOfBlock + elif self.indent == 0 and type not in (tokenize.COMMENT, tokenize.NL): + # any other token on the same indentation level end the previous + # block as well, except the pseudo-tokens COMMENT and NL. + raise EndOfBlock + +def getblock(lines): + """Extract the block of code at the top of the given list of lines.""" + blockfinder = BlockFinder() + try: + tokens = tokenize.generate_tokens(iter(lines).__next__) + for _token in tokens: + blockfinder.tokeneater(*_token) + except (EndOfBlock, IndentationError): + pass + return lines[:blockfinder.last] + +def getsourcelines(object): + """Return a list of source lines and starting line number for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a list of the lines + corresponding to the object and the line number indicates where in the + original source file the first line of code was found. An OSError is + raised if the source code cannot be retrieved.""" + object = unwrap(object) + lines, lnum = findsource(object) + + if istraceback(object): + object = object.tb_frame + + # for module or frame that corresponds to module, return all source lines + if (ismodule(object) or + (isframe(object) and object.f_code.co_name == "")): + return lines, 0 + else: + return getblock(lines[lnum:]), lnum + 1 + +def getsource(object): + """Return the text of the source code for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a single string. An + OSError is raised if the source code cannot be retrieved.""" + lines, lnum = getsourcelines(object) + return ''.join(lines) + +# --------------------------------------------------- class tree extraction +def walktree(classes, children, parent): + """Recursive helper function for getclasstree().""" + results = [] + classes.sort(key=attrgetter('__module__', '__name__')) + for c in classes: + results.append((c, c.__bases__)) + if c in children: + results.append(walktree(children[c], children, c)) + return results + +def getclasstree(classes, unique=False): + """Arrange the given list of classes into a hierarchy of nested lists. + + Where a nested list appears, it contains classes derived from the class + whose entry immediately precedes the list. Each entry is a 2-tuple + containing a class and a tuple of its base classes. If the 'unique' + argument is true, exactly one entry appears in the returned structure + for each class in the given list. Otherwise, classes using multiple + inheritance and their descendants will appear multiple times.""" + children = {} + roots = [] + for c in classes: + if c.__bases__: + for parent in c.__bases__: + if not parent in children: + children[parent] = [] + if c not in children[parent]: + children[parent].append(c) + if unique and parent in classes: break + elif c not in roots: + roots.append(c) + for parent in children: + if parent not in classes: + roots.append(parent) + return walktree(roots, children, None) + +# ------------------------------------------------ argument list extraction +Arguments = namedtuple('Arguments', 'args, varargs, varkw') + +def getargs(co): + """Get information about the arguments accepted by a code object. + + Three things are returned: (args, varargs, varkw), where + 'args' is the list of argument names. Keyword-only arguments are + appended. 'varargs' and 'varkw' are the names of the * and ** + arguments or None.""" + args, varargs, kwonlyargs, varkw = _getfullargs(co) + return Arguments(args + kwonlyargs, varargs, varkw) + +def _getfullargs(co): + """Get information about the arguments accepted by a code object. + + Four things are returned: (args, varargs, kwonlyargs, varkw), where + 'args' and 'kwonlyargs' are lists of argument names, and 'varargs' + and 'varkw' are the names of the * and ** arguments or None.""" + + if not iscode(co): + raise TypeError('{!r} is not a code object'.format(co)) + + nargs = co.co_argcount + names = co.co_varnames + nkwargs = co.co_kwonlyargcount + args = list(names[:nargs]) + kwonlyargs = list(names[nargs:nargs+nkwargs]) + step = 0 + + nargs += nkwargs + varargs = None + if co.co_flags & CO_VARARGS: + varargs = co.co_varnames[nargs] + nargs = nargs + 1 + varkw = None + if co.co_flags & CO_VARKEYWORDS: + varkw = co.co_varnames[nargs] + return args, varargs, kwonlyargs, varkw + + +ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults') + +def getargspec(func): + """Get the names and default values of a function's parameters. + + A tuple of four things is returned: (args, varargs, keywords, defaults). + 'args' is a list of the argument names, including keyword-only argument names. + 'varargs' and 'keywords' are the names of the * and ** parameters or None. + 'defaults' is an n-tuple of the default values of the last n parameters. + + This function is deprecated, as it does not support annotations or + keyword-only parameters and will raise ValueError if either is present + on the supplied callable. + + For a more structured introspection API, use inspect.signature() instead. + + Alternatively, use getfullargspec() for an API with a similar namedtuple + based interface, but full support for annotations and keyword-only + parameters. + + Deprecated since Python 3.5, use `inspect.getfullargspec()`. + """ + warnings.warn("inspect.getargspec() is deprecated since Python 3.0, " + "use inspect.signature() or inspect.getfullargspec()", + DeprecationWarning, stacklevel=2) + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \ + getfullargspec(func) + if kwonlyargs or ann: + raise ValueError("Function has keyword-only parameters or annotations" + ", use getfullargspec() API which can support them") + return ArgSpec(args, varargs, varkw, defaults) + +FullArgSpec = namedtuple('FullArgSpec', + 'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations') + +def getfullargspec(func): + """Get the names and default values of a callable object's parameters. + + A tuple of seven things is returned: + (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations). + 'args' is a list of the parameter names. + 'varargs' and 'varkw' are the names of the * and ** parameters or None. + 'defaults' is an n-tuple of the default values of the last n parameters. + 'kwonlyargs' is a list of keyword-only parameter names. + 'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults. + 'annotations' is a dictionary mapping parameter names to annotations. + + Notable differences from inspect.signature(): + - the "self" parameter is always reported, even for bound methods + - wrapper chains defined by __wrapped__ *not* unwrapped automatically + """ + + try: + # Re: `skip_bound_arg=False` + # + # There is a notable difference in behaviour between getfullargspec + # and Signature: the former always returns 'self' parameter for bound + # methods, whereas the Signature always shows the actual calling + # signature of the passed object. + # + # To simulate this behaviour, we "unbind" bound methods, to trick + # inspect.signature to always return their first parameter ("self", + # usually) + + # Re: `follow_wrapper_chains=False` + # + # getfullargspec() historically ignored __wrapped__ attributes, + # so we ensure that remains the case in 3.3+ + + sig = _signature_from_callable(func, + follow_wrapper_chains=False, + skip_bound_arg=False, + sigcls=Signature) + except Exception as ex: + # Most of the times 'signature' will raise ValueError. + # But, it can also raise AttributeError, and, maybe something + # else. So to be fully backwards compatible, we catch all + # possible exceptions here, and reraise a TypeError. + raise TypeError('unsupported callable') from ex + + args = [] + varargs = None + varkw = None + kwonlyargs = [] + defaults = () + annotations = {} + defaults = () + kwdefaults = {} + + if sig.return_annotation is not sig.empty: + annotations['return'] = sig.return_annotation + + for param in sig.parameters.values(): + kind = param.kind + name = param.name + + if kind is _POSITIONAL_ONLY: + args.append(name) + elif kind is _POSITIONAL_OR_KEYWORD: + args.append(name) + if param.default is not param.empty: + defaults += (param.default,) + elif kind is _VAR_POSITIONAL: + varargs = name + elif kind is _KEYWORD_ONLY: + kwonlyargs.append(name) + if param.default is not param.empty: + kwdefaults[name] = param.default + elif kind is _VAR_KEYWORD: + varkw = name + + if param.annotation is not param.empty: + annotations[name] = param.annotation + + if not kwdefaults: + # compatibility with 'func.__kwdefaults__' + kwdefaults = None + + if not defaults: + # compatibility with 'func.__defaults__' + defaults = None + + return FullArgSpec(args, varargs, varkw, defaults, + kwonlyargs, kwdefaults, annotations) + + +ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals') + +def getargvalues(frame): + """Get information about arguments passed into a particular frame. + + A tuple of four things is returned: (args, varargs, varkw, locals). + 'args' is a list of the argument names. + 'varargs' and 'varkw' are the names of the * and ** arguments or None. + 'locals' is the locals dictionary of the given frame.""" + args, varargs, varkw = getargs(frame.f_code) + return ArgInfo(args, varargs, varkw, frame.f_locals) + +def formatannotation(annotation, base_module=None): + if getattr(annotation, '__module__', None) == 'typing': + return repr(annotation).replace('typing.', '') + if isinstance(annotation, type): + if annotation.__module__ in ('builtins', base_module): + return annotation.__qualname__ + return annotation.__module__+'.'+annotation.__qualname__ + return repr(annotation) + +def formatannotationrelativeto(object): + module = getattr(object, '__module__', None) + def _formatannotation(annotation): + return formatannotation(annotation, module) + return _formatannotation + +def formatargspec(args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value), + formatreturns=lambda text: ' -> ' + text, + formatannotation=formatannotation): + """Format an argument spec from the values returned by getfullargspec. + + The first seven arguments are (args, varargs, varkw, defaults, + kwonlyargs, kwonlydefaults, annotations). The other five arguments + are the corresponding optional formatting functions that are called to + turn names and values into strings. The last argument is an optional + function to format the sequence of arguments.""" + def formatargandannotation(arg): + result = formatarg(arg) + if arg in annotations: + result += ': ' + formatannotation(annotations[arg]) + return result + specs = [] + if defaults: + firstdefault = len(args) - len(defaults) + for i, arg in enumerate(args): + spec = formatargandannotation(arg) + if defaults and i >= firstdefault: + spec = spec + formatvalue(defaults[i - firstdefault]) + specs.append(spec) + if varargs is not None: + specs.append(formatvarargs(formatargandannotation(varargs))) + else: + if kwonlyargs: + specs.append('*') + if kwonlyargs: + for kwonlyarg in kwonlyargs: + spec = formatargandannotation(kwonlyarg) + if kwonlydefaults and kwonlyarg in kwonlydefaults: + spec += formatvalue(kwonlydefaults[kwonlyarg]) + specs.append(spec) + if varkw is not None: + specs.append(formatvarkw(formatargandannotation(varkw))) + result = '(' + ', '.join(specs) + ')' + if 'return' in annotations: + result += formatreturns(formatannotation(annotations['return'])) + return result + +def formatargvalues(args, varargs, varkw, locals, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value)): + """Format an argument spec from the 4 values returned by getargvalues. + + The first four arguments are (args, varargs, varkw, locals). The + next four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. The ninth + argument is an optional function to format the sequence of arguments.""" + def convert(name, locals=locals, + formatarg=formatarg, formatvalue=formatvalue): + return formatarg(name) + formatvalue(locals[name]) + specs = [] + for i in range(len(args)): + specs.append(convert(args[i])) + if varargs: + specs.append(formatvarargs(varargs) + formatvalue(locals[varargs])) + if varkw: + specs.append(formatvarkw(varkw) + formatvalue(locals[varkw])) + return '(' + ', '.join(specs) + ')' + +def _missing_arguments(f_name, argnames, pos, values): + names = [repr(name) for name in argnames if name not in values] + missing = len(names) + if missing == 1: + s = names[0] + elif missing == 2: + s = "{} and {}".format(*names) + else: + tail = ", {} and {}".format(*names[-2:]) + del names[-2:] + s = ", ".join(names) + tail + raise TypeError("%s() missing %i required %s argument%s: %s" % + (f_name, missing, + "positional" if pos else "keyword-only", + "" if missing == 1 else "s", s)) + +def _too_many(f_name, args, kwonly, varargs, defcount, given, values): + atleast = len(args) - defcount + kwonly_given = len([arg for arg in kwonly if arg in values]) + if varargs: + plural = atleast != 1 + sig = "at least %d" % (atleast,) + elif defcount: + plural = True + sig = "from %d to %d" % (atleast, len(args)) + else: + plural = len(args) != 1 + sig = str(len(args)) + kwonly_sig = "" + if kwonly_given: + msg = " positional argument%s (and %d keyword-only argument%s)" + kwonly_sig = (msg % ("s" if given != 1 else "", kwonly_given, + "s" if kwonly_given != 1 else "")) + raise TypeError("%s() takes %s positional argument%s but %d%s %s given" % + (f_name, sig, "s" if plural else "", given, kwonly_sig, + "was" if given == 1 and not kwonly_given else "were")) + +def getcallargs(*func_and_positional, **named): + """Get the mapping of arguments to values. + + A dict is returned, with keys the function argument names (including the + names of the * and ** arguments, if any), and values the respective bound + values from 'positional' and 'named'.""" + func = func_and_positional[0] + positional = func_and_positional[1:] + spec = getfullargspec(func) + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec + f_name = func.__name__ + arg2value = {} + + + if ismethod(func) and func.__self__ is not None: + # implicit 'self' (or 'cls' for classmethods) argument + positional = (func.__self__,) + positional + num_pos = len(positional) + num_args = len(args) + num_defaults = len(defaults) if defaults else 0 + + n = min(num_pos, num_args) + for i in range(n): + arg2value[args[i]] = positional[i] + if varargs: + arg2value[varargs] = tuple(positional[n:]) + possible_kwargs = set(args + kwonlyargs) + if varkw: + arg2value[varkw] = {} + for kw, value in named.items(): + if kw not in possible_kwargs: + if not varkw: + raise TypeError("%s() got an unexpected keyword argument %r" % + (f_name, kw)) + arg2value[varkw][kw] = value + continue + if kw in arg2value: + raise TypeError("%s() got multiple values for argument %r" % + (f_name, kw)) + arg2value[kw] = value + if num_pos > num_args and not varargs: + _too_many(f_name, args, kwonlyargs, varargs, num_defaults, + num_pos, arg2value) + if num_pos < num_args: + req = args[:num_args - num_defaults] + for arg in req: + if arg not in arg2value: + _missing_arguments(f_name, req, True, arg2value) + for i, arg in enumerate(args[num_args - num_defaults:]): + if arg not in arg2value: + arg2value[arg] = defaults[i] + missing = 0 + for kwarg in kwonlyargs: + if kwarg not in arg2value: + if kwonlydefaults and kwarg in kwonlydefaults: + arg2value[kwarg] = kwonlydefaults[kwarg] + else: + missing += 1 + if missing: + _missing_arguments(f_name, kwonlyargs, False, arg2value) + return arg2value + +ClosureVars = namedtuple('ClosureVars', 'nonlocals globals builtins unbound') + +def getclosurevars(func): + """ + Get the mapping of free variables to their current values. + + Returns a named tuple of dicts mapping the current nonlocal, global + and builtin references as seen by the body of the function. A final + set of unbound names that could not be resolved is also provided. + """ + + if ismethod(func): + func = func.__func__ + + if not isfunction(func): + raise TypeError("'{!r}' is not a Python function".format(func)) + + code = func.__code__ + # Nonlocal references are named in co_freevars and resolved + # by looking them up in __closure__ by positional index + if func.__closure__ is None: + nonlocal_vars = {} + else: + nonlocal_vars = { + var : cell.cell_contents + for var, cell in zip(code.co_freevars, func.__closure__) + } + + # Global and builtin references are named in co_names and resolved + # by looking them up in __globals__ or __builtins__ + global_ns = func.__globals__ + builtin_ns = global_ns.get("__builtins__", builtins.__dict__) + if ismodule(builtin_ns): + builtin_ns = builtin_ns.__dict__ + global_vars = {} + builtin_vars = {} + unbound_names = set() + for name in code.co_names: + if name in ("None", "True", "False"): + # Because these used to be builtins instead of keywords, they + # may still show up as name references. We ignore them. + continue + try: + global_vars[name] = global_ns[name] + except KeyError: + try: + builtin_vars[name] = builtin_ns[name] + except KeyError: + unbound_names.add(name) + + return ClosureVars(nonlocal_vars, global_vars, + builtin_vars, unbound_names) + +# -------------------------------------------------- stack frame extraction + +Traceback = namedtuple('Traceback', 'filename lineno function code_context index') + +def getframeinfo(frame, context=1): + """Get information about a frame or traceback object. + + A tuple of five things is returned: the filename, the line number of + the current line, the function name, a list of lines of context from + the source code, and the index of the current line within that list. + The optional second argument specifies the number of lines of context + to return, which are centered around the current line.""" + if istraceback(frame): + lineno = frame.tb_lineno + frame = frame.tb_frame + else: + lineno = frame.f_lineno + if not isframe(frame): + raise TypeError('{!r} is not a frame or traceback object'.format(frame)) + + filename = getsourcefile(frame) or getfile(frame) + if context > 0: + start = lineno - 1 - context//2 + try: + lines, lnum = findsource(frame) + except OSError: + lines = index = None + else: + start = max(0, min(start, len(lines) - context)) + lines = lines[start:start+context] + index = lineno - 1 - start + else: + lines = index = None + + return Traceback(filename, lineno, frame.f_code.co_name, lines, index) + +def getlineno(frame): + """Get the line number from a frame object, allowing for optimization.""" + # FrameType.f_lineno is now a descriptor that grovels co_lnotab + return frame.f_lineno + +FrameInfo = namedtuple('FrameInfo', ('frame',) + Traceback._fields) + +def getouterframes(frame, context=1): + """Get a list of records for a frame and all higher (calling) frames. + + Each record contains a frame object, filename, line number, function + name, a list of lines of context, and index within the context.""" + framelist = [] + while frame: + frameinfo = (frame,) + getframeinfo(frame, context) + framelist.append(FrameInfo(*frameinfo)) + frame = frame.f_back + return framelist + +def getinnerframes(tb, context=1): + """Get a list of records for a traceback's frame and all lower frames. + + Each record contains a frame object, filename, line number, function + name, a list of lines of context, and index within the context.""" + framelist = [] + while tb: + frameinfo = (tb.tb_frame,) + getframeinfo(tb, context) + framelist.append(FrameInfo(*frameinfo)) + tb = tb.tb_next + return framelist + +def currentframe(): + """Return the frame of the caller or None if this is not possible.""" + return sys._getframe(1) if hasattr(sys, "_getframe") else None + +def stack(context=1): + """Return a list of records for the stack above the caller's frame.""" + return getouterframes(sys._getframe(1), context) + +def trace(context=1): + """Return a list of records for the stack below the current exception.""" + return getinnerframes(sys.exc_info()[2], context) + + +# ------------------------------------------------ static version of getattr + +_sentinel = object() + +def _static_getmro(klass): + return type.__dict__['__mro__'].__get__(klass) + +def _check_instance(obj, attr): + instance_dict = {} + try: + instance_dict = object.__getattribute__(obj, "__dict__") + except AttributeError: + pass + return dict.get(instance_dict, attr, _sentinel) + + +def _check_class(klass, attr): + for entry in _static_getmro(klass): + if _shadowed_dict(type(entry)) is _sentinel: + try: + return entry.__dict__[attr] + except KeyError: + pass + return _sentinel + +def _is_type(obj): + try: + _static_getmro(obj) + except TypeError: + return False + return True + +def _shadowed_dict(klass): + dict_attr = type.__dict__["__dict__"] + for entry in _static_getmro(klass): + try: + class_dict = dict_attr.__get__(entry)["__dict__"] + except KeyError: + pass + else: + if not (type(class_dict) is types.GetSetDescriptorType and + class_dict.__name__ == "__dict__" and + class_dict.__objclass__ is entry): + return class_dict + return _sentinel + +def getattr_static(obj, attr, default=_sentinel): + """Retrieve attributes without triggering dynamic lookup via the + descriptor protocol, __getattr__ or __getattribute__. + + Note: this function may not be able to retrieve all attributes + that getattr can fetch (like dynamically created attributes) + and may find attributes that getattr can't (like descriptors + that raise AttributeError). It can also return descriptor objects + instead of instance members in some cases. See the + documentation for details. + """ + instance_result = _sentinel + if not _is_type(obj): + klass = type(obj) + dict_attr = _shadowed_dict(klass) + if (dict_attr is _sentinel or + type(dict_attr) is types.MemberDescriptorType): + instance_result = _check_instance(obj, attr) + else: + klass = obj + + klass_result = _check_class(klass, attr) + + if instance_result is not _sentinel and klass_result is not _sentinel: + if (_check_class(type(klass_result), '__get__') is not _sentinel and + _check_class(type(klass_result), '__set__') is not _sentinel): + return klass_result + + if instance_result is not _sentinel: + return instance_result + if klass_result is not _sentinel: + return klass_result + + if obj is klass: + # for types we check the metaclass too + for entry in _static_getmro(type(klass)): + if _shadowed_dict(type(entry)) is _sentinel: + try: + return entry.__dict__[attr] + except KeyError: + pass + if default is not _sentinel: + return default + raise AttributeError(attr) + + +# ------------------------------------------------ generator introspection + +GEN_CREATED = 'GEN_CREATED' +GEN_RUNNING = 'GEN_RUNNING' +GEN_SUSPENDED = 'GEN_SUSPENDED' +GEN_CLOSED = 'GEN_CLOSED' + +def getgeneratorstate(generator): + """Get current state of a generator-iterator. + + Possible states are: + GEN_CREATED: Waiting to start execution. + GEN_RUNNING: Currently being executed by the interpreter. + GEN_SUSPENDED: Currently suspended at a yield expression. + GEN_CLOSED: Execution has completed. + """ + if generator.gi_running: + return GEN_RUNNING + if generator.gi_frame is None: + return GEN_CLOSED + if generator.gi_frame.f_lasti == -1: + return GEN_CREATED + return GEN_SUSPENDED + + +def getgeneratorlocals(generator): + """ + Get the mapping of generator local variables to their current values. + + A dict is returned, with the keys the local variable names and values the + bound values.""" + + if not isgenerator(generator): + raise TypeError("'{!r}' is not a Python generator".format(generator)) + + frame = getattr(generator, "gi_frame", None) + if frame is not None: + return generator.gi_frame.f_locals + else: + return {} + + +# ------------------------------------------------ coroutine introspection + +CORO_CREATED = 'CORO_CREATED' +CORO_RUNNING = 'CORO_RUNNING' +CORO_SUSPENDED = 'CORO_SUSPENDED' +CORO_CLOSED = 'CORO_CLOSED' + +def getcoroutinestate(coroutine): + """Get current state of a coroutine object. + + Possible states are: + CORO_CREATED: Waiting to start execution. + CORO_RUNNING: Currently being executed by the interpreter. + CORO_SUSPENDED: Currently suspended at an await expression. + CORO_CLOSED: Execution has completed. + """ + if coroutine.cr_running: + return CORO_RUNNING + if coroutine.cr_frame is None: + return CORO_CLOSED + if coroutine.cr_frame.f_lasti == -1: + return CORO_CREATED + return CORO_SUSPENDED + + +def getcoroutinelocals(coroutine): + """ + Get the mapping of coroutine local variables to their current values. + + A dict is returned, with the keys the local variable names and values the + bound values.""" + frame = getattr(coroutine, "cr_frame", None) + if frame is not None: + return frame.f_locals + else: + return {} + + +############################################################################### +### Function Signature Object (PEP 362) +############################################################################### + + +_WrapperDescriptor = type(type.__call__) +_MethodWrapper = type(all.__call__) +_ClassMethodWrapper = type(int.__dict__['from_bytes']) + +_NonUserDefinedCallables = (_WrapperDescriptor, + _MethodWrapper, + _ClassMethodWrapper, + types.BuiltinFunctionType) + + +def _signature_get_user_defined_method(cls, method_name): + """Private helper. Checks if ``cls`` has an attribute + named ``method_name`` and returns it only if it is a + pure python function. + """ + try: + meth = getattr(cls, method_name) + except AttributeError: + return + else: + if not isinstance(meth, _NonUserDefinedCallables): + # Once '__signature__' will be added to 'C'-level + # callables, this check won't be necessary + return meth + + +def _signature_get_partial(wrapped_sig, partial, extra_args=()): + """Private helper to calculate how 'wrapped_sig' signature will + look like after applying a 'functools.partial' object (or alike) + on it. + """ + + old_params = wrapped_sig.parameters + new_params = OrderedDict(old_params.items()) + + partial_args = partial.args or () + partial_keywords = partial.keywords or {} + + if extra_args: + partial_args = extra_args + partial_args + + try: + ba = wrapped_sig.bind_partial(*partial_args, **partial_keywords) + except TypeError as ex: + msg = 'partial object {!r} has incorrect arguments'.format(partial) + raise ValueError(msg) from ex + + + transform_to_kwonly = False + for param_name, param in old_params.items(): + try: + arg_value = ba.arguments[param_name] + except KeyError: + pass + else: + if param.kind is _POSITIONAL_ONLY: + # If positional-only parameter is bound by partial, + # it effectively disappears from the signature + new_params.pop(param_name) + continue + + if param.kind is _POSITIONAL_OR_KEYWORD: + if param_name in partial_keywords: + # This means that this parameter, and all parameters + # after it should be keyword-only (and var-positional + # should be removed). Here's why. Consider the following + # function: + # foo(a, b, *args, c): + # pass + # + # "partial(foo, a='spam')" will have the following + # signature: "(*, a='spam', b, c)". Because attempting + # to call that partial with "(10, 20)" arguments will + # raise a TypeError, saying that "a" argument received + # multiple values. + transform_to_kwonly = True + # Set the new default value + new_params[param_name] = param.replace(default=arg_value) + else: + # was passed as a positional argument + new_params.pop(param.name) + continue + + if param.kind is _KEYWORD_ONLY: + # Set the new default value + new_params[param_name] = param.replace(default=arg_value) + + if transform_to_kwonly: + assert param.kind is not _POSITIONAL_ONLY + + if param.kind is _POSITIONAL_OR_KEYWORD: + new_param = new_params[param_name].replace(kind=_KEYWORD_ONLY) + new_params[param_name] = new_param + new_params.move_to_end(param_name) + elif param.kind in (_KEYWORD_ONLY, _VAR_KEYWORD): + new_params.move_to_end(param_name) + elif param.kind is _VAR_POSITIONAL: + new_params.pop(param.name) + + return wrapped_sig.replace(parameters=new_params.values()) + + +def _signature_bound_method(sig): + """Private helper to transform signatures for unbound + functions to bound methods. + """ + + params = tuple(sig.parameters.values()) + + if not params or params[0].kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + raise ValueError('invalid method signature') + + kind = params[0].kind + if kind in (_POSITIONAL_OR_KEYWORD, _POSITIONAL_ONLY): + # Drop first parameter: + # '(p1, p2[, ...])' -> '(p2[, ...])' + params = params[1:] + else: + if kind is not _VAR_POSITIONAL: + # Unless we add a new parameter type we never + # get here + raise ValueError('invalid argument type') + # It's a var-positional parameter. + # Do nothing. '(*args[, ...])' -> '(*args[, ...])' + + return sig.replace(parameters=params) + + +def _signature_is_builtin(obj): + """Private helper to test if `obj` is a callable that might + support Argument Clinic's __text_signature__ protocol. + """ + return (isbuiltin(obj) or + ismethoddescriptor(obj) or + isinstance(obj, _NonUserDefinedCallables) or + # Can't test 'isinstance(type)' here, as it would + # also be True for regular python classes + obj in (type, object)) + + +def _signature_is_functionlike(obj): + """Private helper to test if `obj` is a duck type of FunctionType. + A good example of such objects are functions compiled with + Cython, which have all attributes that a pure Python function + would have, but have their code statically compiled. + """ + + if not callable(obj) or isclass(obj): + # All function-like objects are obviously callables, + # and not classes. + return False + + name = getattr(obj, '__name__', None) + code = getattr(obj, '__code__', None) + defaults = getattr(obj, '__defaults__', _void) # Important to use _void ... + kwdefaults = getattr(obj, '__kwdefaults__', _void) # ... and not None here + annotations = getattr(obj, '__annotations__', None) + + return (isinstance(code, types.CodeType) and + isinstance(name, str) and + (defaults is None or isinstance(defaults, tuple)) and + (kwdefaults is None or isinstance(kwdefaults, dict)) and + isinstance(annotations, dict)) + + +def _signature_get_bound_param(spec): + """ Private helper to get first parameter name from a + __text_signature__ of a builtin method, which should + be in the following format: '($param1, ...)'. + Assumptions are that the first argument won't have + a default value or an annotation. + """ + + assert spec.startswith('($') + + pos = spec.find(',') + if pos == -1: + pos = spec.find(')') + + cpos = spec.find(':') + assert cpos == -1 or cpos > pos + + cpos = spec.find('=') + assert cpos == -1 or cpos > pos + + return spec[2:pos] + + +def _signature_strip_non_python_syntax(signature): + """ + Private helper function. Takes a signature in Argument Clinic's + extended signature format. + + Returns a tuple of three things: + * that signature re-rendered in standard Python syntax, + * the index of the "self" parameter (generally 0), or None if + the function does not have a "self" parameter, and + * the index of the last "positional only" parameter, + or None if the signature has no positional-only parameters. + """ + + if not signature: + return signature, None, None + + self_parameter = None + last_positional_only = None + + lines = [l.encode('ascii') for l in signature.split('\n')] + generator = iter(lines).__next__ + token_stream = tokenize.tokenize(generator) + + delayed_comma = False + skip_next_comma = False + text = [] + add = text.append + + current_parameter = 0 + OP = token.OP + ERRORTOKEN = token.ERRORTOKEN + + # token stream always starts with ENCODING token, skip it + t = next(token_stream) + assert t.type == tokenize.ENCODING + + for t in token_stream: + type, string = t.type, t.string + + if type == OP: + if string == ',': + if skip_next_comma: + skip_next_comma = False + else: + assert not delayed_comma + delayed_comma = True + current_parameter += 1 + continue + + if string == '/': + assert not skip_next_comma + assert last_positional_only is None + skip_next_comma = True + last_positional_only = current_parameter - 1 + continue + + if (type == ERRORTOKEN) and (string == '$'): + assert self_parameter is None + self_parameter = current_parameter + continue + + if delayed_comma: + delayed_comma = False + if not ((type == OP) and (string == ')')): + add(', ') + add(string) + if (string == ','): + add(' ') + clean_signature = ''.join(text) + return clean_signature, self_parameter, last_positional_only + + +def _signature_fromstr(cls, obj, s, skip_bound_arg=True): + """Private helper to parse content of '__text_signature__' + and return a Signature based on it. + """ + + Parameter = cls._parameter_cls + + clean_signature, self_parameter, last_positional_only = \ + _signature_strip_non_python_syntax(s) + + program = "def foo" + clean_signature + ": pass" + + try: + module = ast.parse(program) + except SyntaxError: + module = None + + if not isinstance(module, ast.Module): + raise ValueError("{!r} builtin has invalid signature".format(obj)) + + f = module.body[0] + + parameters = [] + empty = Parameter.empty + invalid = object() + + module = None + module_dict = {} + module_name = getattr(obj, '__module__', None) + if module_name: + module = sys.modules.get(module_name, None) + if module: + module_dict = module.__dict__ + sys_module_dict = sys.modules.copy() + + def parse_name(node): + assert isinstance(node, ast.arg) + if node.annotation != None: + raise ValueError("Annotations are not currently supported") + return node.arg + + def wrap_value(s): + try: + value = eval(s, module_dict) + except NameError: + try: + value = eval(s, sys_module_dict) + except NameError: + raise RuntimeError() + + if isinstance(value, str): + return ast.Str(value) + if isinstance(value, (int, float)): + return ast.Num(value) + if isinstance(value, bytes): + return ast.Bytes(value) + if value in (True, False, None): + return ast.NameConstant(value) + raise RuntimeError() + + class RewriteSymbolics(ast.NodeTransformer): + def visit_Attribute(self, node): + a = [] + n = node + while isinstance(n, ast.Attribute): + a.append(n.attr) + n = n.value + if not isinstance(n, ast.Name): + raise RuntimeError() + a.append(n.id) + value = ".".join(reversed(a)) + return wrap_value(value) + + def visit_Name(self, node): + if not isinstance(node.ctx, ast.Load): + raise ValueError() + return wrap_value(node.id) + + def p(name_node, default_node, default=empty): + name = parse_name(name_node) + if name is invalid: + return None + if default_node and default_node is not _empty: + try: + default_node = RewriteSymbolics().visit(default_node) + o = ast.literal_eval(default_node) + except ValueError: + o = invalid + if o is invalid: + return None + default = o if o is not invalid else default + parameters.append(Parameter(name, kind, default=default, annotation=empty)) + + # non-keyword-only parameters + args = reversed(f.args.args) + defaults = reversed(f.args.defaults) + iter = itertools.zip_longest(args, defaults, fillvalue=None) + if last_positional_only is not None: + kind = Parameter.POSITIONAL_ONLY + else: + kind = Parameter.POSITIONAL_OR_KEYWORD + for i, (name, default) in enumerate(reversed(list(iter))): + p(name, default) + if i == last_positional_only: + kind = Parameter.POSITIONAL_OR_KEYWORD + + # *args + if f.args.vararg: + kind = Parameter.VAR_POSITIONAL + p(f.args.vararg, empty) + + # keyword-only arguments + kind = Parameter.KEYWORD_ONLY + for name, default in zip(f.args.kwonlyargs, f.args.kw_defaults): + p(name, default) + + # **kwargs + if f.args.kwarg: + kind = Parameter.VAR_KEYWORD + p(f.args.kwarg, empty) + + if self_parameter is not None: + # Possibly strip the bound argument: + # - We *always* strip first bound argument if + # it is a module. + # - We don't strip first bound argument if + # skip_bound_arg is False. + assert parameters + _self = getattr(obj, '__self__', None) + self_isbound = _self is not None + self_ismodule = ismodule(_self) + if self_isbound and (self_ismodule or skip_bound_arg): + parameters.pop(0) + else: + # for builtins, self parameter is always positional-only! + p = parameters[0].replace(kind=Parameter.POSITIONAL_ONLY) + parameters[0] = p + + return cls(parameters, return_annotation=cls.empty) + + +def _signature_from_builtin(cls, func, skip_bound_arg=True): + """Private helper function to get signature for + builtin callables. + """ + + if not _signature_is_builtin(func): + raise TypeError("{!r} is not a Python builtin " + "function".format(func)) + + s = getattr(func, "__text_signature__", None) + if not s: + raise ValueError("no signature found for builtin {!r}".format(func)) + + return _signature_fromstr(cls, func, s, skip_bound_arg) + + +def _signature_from_function(cls, func): + """Private helper: constructs Signature for the given python function.""" + + is_duck_function = False + if not isfunction(func): + if _signature_is_functionlike(func): + is_duck_function = True + else: + # If it's not a pure Python function, and not a duck type + # of pure function: + raise TypeError('{!r} is not a Python function'.format(func)) + + Parameter = cls._parameter_cls + + # Parameter information. + func_code = func.__code__ + pos_count = func_code.co_argcount + arg_names = func_code.co_varnames + positional = tuple(arg_names[:pos_count]) + keyword_only_count = func_code.co_kwonlyargcount + keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)] + annotations = func.__annotations__ + defaults = func.__defaults__ + kwdefaults = func.__kwdefaults__ + + if defaults: + pos_default_count = len(defaults) + else: + pos_default_count = 0 + + parameters = [] + + # Non-keyword-only parameters w/o defaults. + non_default_count = pos_count - pos_default_count + for name in positional[:non_default_count]: + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_POSITIONAL_OR_KEYWORD)) + + # ... w/ defaults. + for offset, name in enumerate(positional[non_default_count:]): + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_POSITIONAL_OR_KEYWORD, + default=defaults[offset])) + + # *args + if func_code.co_flags & CO_VARARGS: + name = arg_names[pos_count + keyword_only_count] + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_VAR_POSITIONAL)) + + # Keyword-only parameters. + for name in keyword_only: + default = _empty + if kwdefaults is not None: + default = kwdefaults.get(name, _empty) + + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_KEYWORD_ONLY, + default=default)) + # **kwargs + if func_code.co_flags & CO_VARKEYWORDS: + index = pos_count + keyword_only_count + if func_code.co_flags & CO_VARARGS: + index += 1 + + name = arg_names[index] + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_VAR_KEYWORD)) + + # Is 'func' is a pure Python function - don't validate the + # parameters list (for correct order and defaults), it should be OK. + return cls(parameters, + return_annotation=annotations.get('return', _empty), + __validate_parameters__=is_duck_function) + + +def _signature_from_callable(obj, *, + follow_wrapper_chains=True, + skip_bound_arg=True, + sigcls): + + """Private helper function to get signature for arbitrary + callable objects. + """ + + if not callable(obj): + raise TypeError('{!r} is not a callable object'.format(obj)) + + if isinstance(obj, types.MethodType): + print ("Ok") + # In this case we skip the first parameter of the underlying + # function (usually `self` or `cls`). + sig = _signature_from_callable( + obj.__func__, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + + if skip_bound_arg: + print ("1") + return _signature_bound_method(sig) + else: + print ("2") + return sig + + # Was this function wrapped by a decorator? + if follow_wrapper_chains: + obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__"))) + if isinstance(obj, types.MethodType): + # If the unwrapped object is a *method*, we might want to + # skip its first parameter (self). + # See test_signature_wrapped_bound_method for details. + return _signature_from_callable( + obj, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + + try: + sig = obj.__signature__ + except AttributeError: + pass + else: + if sig is not None: + if not isinstance(sig, Signature): + raise TypeError( + 'unexpected object {!r} in __signature__ ' + 'attribute'.format(sig)) + return sig + + try: + partialmethod = obj._partialmethod + except AttributeError: + pass + else: + if isinstance(partialmethod, functools.partialmethod): + # Unbound partialmethod (see functools.partialmethod) + # This means, that we need to calculate the signature + # as if it's a regular partial object, but taking into + # account that the first positional argument + # (usually `self`, or `cls`) will not be passed + # automatically (as for boundmethods) + + wrapped_sig = _signature_from_callable( + partialmethod.func, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + + sig = _signature_get_partial(wrapped_sig, partialmethod, (None,)) + first_wrapped_param = tuple(wrapped_sig.parameters.values())[0] + if first_wrapped_param.kind is Parameter.VAR_POSITIONAL: + # First argument of the wrapped callable is `*args`, as in + # `partialmethod(lambda *args)`. + return sig + else: + sig_params = tuple(sig.parameters.values()) + assert (not sig_params or + first_wrapped_param is not sig_params[0]) + new_params = (first_wrapped_param,) + sig_params + return sig.replace(parameters=new_params) + + if isfunction(obj) or _signature_is_functionlike(obj): + # If it's a pure Python function, or an object that is duck type + # of a Python function (Cython functions, for instance), then: + return _signature_from_function(sigcls, obj) + + if _signature_is_builtin(obj): + return _signature_from_builtin(sigcls, obj, + skip_bound_arg=skip_bound_arg) + + if isinstance(obj, functools.partial): + wrapped_sig = _signature_from_callable( + obj.func, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + return _signature_get_partial(wrapped_sig, obj) + + sig = None + if isinstance(obj, type): + # obj is a class or a metaclass + + # First, let's see if it has an overloaded __call__ defined + # in its metaclass + call = _signature_get_user_defined_method(type(obj), '__call__') + if call is not None: + sig = _signature_from_callable( + call, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + else: + # Now we check if the 'obj' class has a '__new__' method + new = _signature_get_user_defined_method(obj, '__new__') + if new is not None: + sig = _signature_from_callable( + new, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + else: + # Finally, we should have at least __init__ implemented + init = _signature_get_user_defined_method(obj, '__init__') + if init is not None: + sig = _signature_from_callable( + init, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + + if sig is None: + # At this point we know, that `obj` is a class, with no user- + # defined '__init__', '__new__', or class-level '__call__' + + for base in obj.__mro__[:-1]: + # Since '__text_signature__' is implemented as a + # descriptor that extracts text signature from the + # class docstring, if 'obj' is derived from a builtin + # class, its own '__text_signature__' may be 'None'. + # Therefore, we go through the MRO (except the last + # class in there, which is 'object') to find the first + # class with non-empty text signature. + try: + text_sig = base.__text_signature__ + except AttributeError: + pass + else: + if text_sig: + # If 'obj' class has a __text_signature__ attribute: + # return a signature based on it + return _signature_fromstr(sigcls, obj, text_sig) + + # No '__text_signature__' was found for the 'obj' class. + # Last option is to check if its '__init__' is + # object.__init__ or type.__init__. + if type not in obj.__mro__: + # We have a class (not metaclass), but no user-defined + # __init__ or __new__ for it + if (obj.__init__ is object.__init__ and + obj.__new__ is object.__new__): + # Return a signature of 'object' builtin. + return signature(object) + else: + raise ValueError( + 'no signature found for builtin type {!r}'.format(obj)) + + elif not isinstance(obj, _NonUserDefinedCallables): + # An object with __call__ + # We also check that the 'obj' is not an instance of + # _WrapperDescriptor or _MethodWrapper to avoid + # infinite recursion (and even potential segfault) + call = _signature_get_user_defined_method(type(obj), '__call__') + if call is not None: + try: + sig = _signature_from_callable( + call, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + except ValueError as ex: + msg = 'no signature found for {!r}'.format(obj) + raise ValueError(msg) from ex + + if sig is not None: + # For classes and objects we skip the first parameter of their + # __call__, __new__, or __init__ methods + if skip_bound_arg: + return _signature_bound_method(sig) + else: + return sig + + if isinstance(obj, types.BuiltinFunctionType): + # Raise a nicer error message for builtins + msg = 'no signature found for builtin function {!r}'.format(obj) + raise ValueError(msg) + + #print (type(obj)) + #raise ValueError('callable {!r} is not supported by signature'.format(obj)) + + +class _void: + """A private marker - used in Parameter & Signature.""" + + +class _empty: + """Marker object for Signature.empty and Parameter.empty.""" + + +class _ParameterKind(enum.IntEnum): + POSITIONAL_ONLY = 0 + POSITIONAL_OR_KEYWORD = 1 + VAR_POSITIONAL = 2 + KEYWORD_ONLY = 3 + VAR_KEYWORD = 4 + + def __str__(self): + return self._name_ + + +_POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY +_POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD +_VAR_POSITIONAL = _ParameterKind.VAR_POSITIONAL +_KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY +_VAR_KEYWORD = _ParameterKind.VAR_KEYWORD +_PARAM_NAME_MAPPING = { + _POSITIONAL_ONLY: 'positional-only', + _POSITIONAL_OR_KEYWORD: 'positional or keyword', + _VAR_POSITIONAL: 'variadic positional', + _KEYWORD_ONLY: 'keyword-only', + _VAR_KEYWORD: 'variadic keyword'} + +_get_paramkind_descr = _PARAM_NAME_MAPPING.__getitem__ + + +class Parameter: + """Represents a parameter in a function signature. + + Has the following public attributes: + + * name : str + The name of the parameter as a string. + * default : object + The default value for the parameter if specified. If the + parameter has no default value, this attribute is set to + `Parameter.empty`. + * annotation + The annotation for the parameter if specified. If the + parameter has no annotation, this attribute is set to + `Parameter.empty`. + * kind : str + Describes how argument values are bound to the parameter. + Possible values: `Parameter.POSITIONAL_ONLY`, + `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`, + `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`. + """ + + __slots__ = ('_name', '_kind', '_default', '_annotation') + + POSITIONAL_ONLY = _POSITIONAL_ONLY + POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD + VAR_POSITIONAL = _VAR_POSITIONAL + KEYWORD_ONLY = _KEYWORD_ONLY + VAR_KEYWORD = _VAR_KEYWORD + + empty = _empty + + def __init__(self, name, kind, *, default=_empty, annotation=_empty): + try: + self._kind = _ParameterKind(kind) + except ValueError: + raise ValueError(f'value {kind!r} is not a valid Parameter.kind') + if default is not _empty: + if self._kind in (_VAR_POSITIONAL, _VAR_KEYWORD): + msg = '{} parameters cannot have default values' + msg = msg.format(_get_paramkind_descr(self._kind)) + raise ValueError(msg) + self._default = default + self._annotation = annotation + + if name is _empty: + raise ValueError('name is a required attribute for Parameter') + + if not isinstance(name, str): + msg = 'name must be a str, not a {}'.format(type(name).__name__) + raise TypeError(msg) + + if name[0] == '.' and name[1:].isdigit(): + # These are implicit arguments generated by comprehensions. In + # order to provide a friendlier interface to users, we recast + # their name as "implicitN" and treat them as positional-only. + # See issue 19611. + if self._kind != _POSITIONAL_OR_KEYWORD: + msg = ( + 'implicit arguments must be passed as ' + 'positional or keyword arguments, not {}' + ) + msg = msg.format(_get_paramkind_descr(self._kind)) + raise ValueError(msg) + self._kind = _POSITIONAL_ONLY + name = 'implicit{}'.format(name[1:]) + + if not name.isidentifier(): + raise ValueError('{!r} is not a valid parameter name'.format(name)) + + self._name = name + + def __reduce__(self): + return (type(self), + (self._name, self._kind), + {'_default': self._default, + '_annotation': self._annotation}) + + def __setstate__(self, state): + self._default = state['_default'] + self._annotation = state['_annotation'] + + @property + def name(self): + return self._name + + @property + def default(self): + return self._default + + @property + def annotation(self): + return self._annotation + + @property + def kind(self): + return self._kind + + def replace(self, *, name=_void, kind=_void, + annotation=_void, default=_void): + """Creates a customized copy of the Parameter.""" + + if name is _void: + name = self._name + + if kind is _void: + kind = self._kind + + if annotation is _void: + annotation = self._annotation + + if default is _void: + default = self._default + + return type(self)(name, kind, default=default, annotation=annotation) + + def __str__(self): + kind = self.kind + formatted = self._name + + # Add annotation and default value + if self._annotation is not _empty: + formatted = '{}:{}'.format(formatted, + formatannotation(self._annotation)) + + if self._default is not _empty: + formatted = '{}={}'.format(formatted, repr(self._default)) + + if kind == _VAR_POSITIONAL: + formatted = '*' + formatted + elif kind == _VAR_KEYWORD: + formatted = '**' + formatted + + return formatted + + def __repr__(self): + return '<{} "{}">'.format(self.__class__.__name__, self) + + def __hash__(self): + return hash((self.name, self.kind, self.annotation, self.default)) + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, Parameter): + return NotImplemented + return (self._name == other._name and + self._kind == other._kind and + self._default == other._default and + self._annotation == other._annotation) + + +class BoundArguments: + """Result of `Signature.bind` call. Holds the mapping of arguments + to the function's parameters. + + Has the following public attributes: + + * arguments : OrderedDict + An ordered mutable mapping of parameters' names to arguments' values. + Does not contain arguments' default values. + * signature : Signature + The Signature object that created this instance. + * args : tuple + Tuple of positional arguments values. + * kwargs : dict + Dict of keyword arguments values. + """ + + __slots__ = ('arguments', '_signature', '__weakref__') + + def __init__(self, signature, arguments): + self.arguments = arguments + self._signature = signature + + @property + def signature(self): + return self._signature + + @property + def args(self): + args = [] + for param_name, param in self._signature.parameters.items(): + if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + break + + try: + arg = self.arguments[param_name] + except KeyError: + # We're done here. Other arguments + # will be mapped in 'BoundArguments.kwargs' + break + else: + if param.kind == _VAR_POSITIONAL: + # *args + args.extend(arg) + else: + # plain argument + args.append(arg) + + return tuple(args) + + @property + def kwargs(self): + kwargs = {} + kwargs_started = False + for param_name, param in self._signature.parameters.items(): + if not kwargs_started: + if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + kwargs_started = True + else: + if param_name not in self.arguments: + kwargs_started = True + continue + + if not kwargs_started: + continue + + try: + arg = self.arguments[param_name] + except KeyError: + pass + else: + if param.kind == _VAR_KEYWORD: + # **kwargs + kwargs.update(arg) + else: + # plain keyword argument + kwargs[param_name] = arg + + return kwargs + + def apply_defaults(self): + """Set default values for missing arguments. + + For variable-positional arguments (*args) the default is an + empty tuple. + + For variable-keyword arguments (**kwargs) the default is an + empty dict. + """ + arguments = self.arguments + new_arguments = [] + for name, param in self._signature.parameters.items(): + try: + new_arguments.append((name, arguments[name])) + except KeyError: + if param.default is not _empty: + val = param.default + elif param.kind is _VAR_POSITIONAL: + val = () + elif param.kind is _VAR_KEYWORD: + val = {} + else: + # This BoundArguments was likely produced by + # Signature.bind_partial(). + continue + new_arguments.append((name, val)) + self.arguments = OrderedDict(new_arguments) + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, BoundArguments): + return NotImplemented + return (self.signature == other.signature and + self.arguments == other.arguments) + + def __setstate__(self, state): + self._signature = state['_signature'] + self.arguments = state['arguments'] + + def __getstate__(self): + return {'_signature': self._signature, 'arguments': self.arguments} + + def __repr__(self): + args = [] + for arg, value in self.arguments.items(): + args.append('{}={!r}'.format(arg, value)) + return '<{} ({})>'.format(self.__class__.__name__, ', '.join(args)) + + +class Signature: + """A Signature object represents the overall signature of a function. + It stores a Parameter object for each parameter accepted by the + function, as well as information specific to the function itself. + + A Signature object has the following public attributes and methods: + + * parameters : OrderedDict + An ordered mapping of parameters' names to the corresponding + Parameter objects (keyword-only arguments are in the same order + as listed in `code.co_varnames`). + * return_annotation : object + The annotation for the return type of the function if specified. + If the function has no annotation for its return type, this + attribute is set to `Signature.empty`. + * bind(*args, **kwargs) -> BoundArguments + Creates a mapping from positional and keyword arguments to + parameters. + * bind_partial(*args, **kwargs) -> BoundArguments + Creates a partial mapping from positional and keyword arguments + to parameters (simulating 'functools.partial' behavior.) + """ + + __slots__ = ('_return_annotation', '_parameters') + + _parameter_cls = Parameter + _bound_arguments_cls = BoundArguments + + empty = _empty + + def __init__(self, parameters=None, *, return_annotation=_empty, + __validate_parameters__=True): + """Constructs Signature from the given list of Parameter + objects and 'return_annotation'. All arguments are optional. + """ + + if parameters is None: + params = OrderedDict() + else: + if __validate_parameters__: + params = OrderedDict() + top_kind = _POSITIONAL_ONLY + kind_defaults = False + + for idx, param in enumerate(parameters): + kind = param.kind + name = param.name + + if kind < top_kind: + msg = ( + 'wrong parameter order: {} parameter before {} ' + 'parameter' + ) + msg = msg.format(_get_paramkind_descr(top_kind), + _get_paramkind_descr(kind)) + raise ValueError(msg) + elif kind > top_kind: + kind_defaults = False + top_kind = kind + + if kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD): + if param.default is _empty: + if kind_defaults: + # No default for this parameter, but the + # previous parameter of the same kind had + # a default + msg = 'non-default argument follows default ' \ + 'argument' + raise ValueError(msg) + else: + # There is a default for this parameter. + kind_defaults = True + + if name in params: + msg = 'duplicate parameter name: {!r}'.format(name) + raise ValueError(msg) + + params[name] = param + else: + params = OrderedDict(((param.name, param) + for param in parameters)) + + self._parameters = types.MappingProxyType(params) + self._return_annotation = return_annotation + + @classmethod + def from_function(cls, func): + """Constructs Signature for the given python function. + + Deprecated since Python 3.5, use `Signature.from_callable()`. + """ + + warnings.warn("inspect.Signature.from_function() is deprecated since " + "Python 3.5, use Signature.from_callable()", + DeprecationWarning, stacklevel=2) + return _signature_from_function(cls, func) + + @classmethod + def from_builtin(cls, func): + """Constructs Signature for the given builtin function. + + Deprecated since Python 3.5, use `Signature.from_callable()`. + """ + + warnings.warn("inspect.Signature.from_builtin() is deprecated since " + "Python 3.5, use Signature.from_callable()", + DeprecationWarning, stacklevel=2) + return _signature_from_builtin(cls, func) + + @classmethod + def from_callable(cls, obj, *, follow_wrapped=True): + """Constructs Signature for the given callable object.""" + return _signature_from_callable(obj, sigcls=cls, + follow_wrapper_chains=follow_wrapped) + + @property + def parameters(self): + return self._parameters + + @property + def return_annotation(self): + return self._return_annotation + + def replace(self, *, parameters=_void, return_annotation=_void): + """Creates a customized copy of the Signature. + Pass 'parameters' and/or 'return_annotation' arguments + to override them in the new copy. + """ + + if parameters is _void: + parameters = self.parameters.values() + + if return_annotation is _void: + return_annotation = self._return_annotation + + return type(self)(parameters, + return_annotation=return_annotation) + + def _hash_basis(self): + params = tuple(param for param in self.parameters.values() + if param.kind != _KEYWORD_ONLY) + + kwo_params = {param.name: param for param in self.parameters.values() + if param.kind == _KEYWORD_ONLY} + + return params, kwo_params, self.return_annotation + + def __hash__(self): + params, kwo_params, return_annotation = self._hash_basis() + kwo_params = frozenset(kwo_params.values()) + return hash((params, kwo_params, return_annotation)) + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, Signature): + return NotImplemented + return self._hash_basis() == other._hash_basis() + + def _bind(self, args, kwargs, *, partial=False): + """Private method. Don't use directly.""" + + arguments = OrderedDict() + + parameters = iter(self.parameters.values()) + parameters_ex = () + arg_vals = iter(args) + + while True: + # Let's iterate through the positional arguments and corresponding + # parameters + try: + arg_val = next(arg_vals) + except StopIteration: + # No more positional arguments + try: + param = next(parameters) + except StopIteration: + # No more parameters. That's it. Just need to check that + # we have no `kwargs` after this while loop + break + else: + if param.kind == _VAR_POSITIONAL: + # That's OK, just empty *args. Let's start parsing + # kwargs + break + elif param.name in kwargs: + if param.kind == _POSITIONAL_ONLY: + msg = '{arg!r} parameter is positional only, ' \ + 'but was passed as a keyword' + msg = msg.format(arg=param.name) + raise TypeError(msg) from None + parameters_ex = (param,) + break + elif (param.kind == _VAR_KEYWORD or + param.default is not _empty): + # That's fine too - we have a default value for this + # parameter. So, lets start parsing `kwargs`, starting + # with the current parameter + parameters_ex = (param,) + break + else: + # No default, not VAR_KEYWORD, not VAR_POSITIONAL, + # not in `kwargs` + if partial: + parameters_ex = (param,) + break + else: + msg = 'missing a required argument: {arg!r}' + msg = msg.format(arg=param.name) + raise TypeError(msg) from None + else: + # We have a positional argument to process + try: + param = next(parameters) + except StopIteration: + raise TypeError('too many positional arguments') from None + else: + if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + # Looks like we have no parameter for this positional + # argument + raise TypeError( + 'too many positional arguments') from None + + if param.kind == _VAR_POSITIONAL: + # We have an '*args'-like argument, let's fill it with + # all positional arguments we have left and move on to + # the next phase + values = [arg_val] + values.extend(arg_vals) + arguments[param.name] = tuple(values) + break + + if param.name in kwargs: + raise TypeError( + 'multiple values for argument {arg!r}'.format( + arg=param.name)) from None + + arguments[param.name] = arg_val + + # Now, we iterate through the remaining parameters to process + # keyword arguments + kwargs_param = None + for param in itertools.chain(parameters_ex, parameters): + if param.kind == _VAR_KEYWORD: + # Memorize that we have a '**kwargs'-like parameter + kwargs_param = param + continue + + if param.kind == _VAR_POSITIONAL: + # Named arguments don't refer to '*args'-like parameters. + # We only arrive here if the positional arguments ended + # before reaching the last parameter before *args. + continue + + param_name = param.name + try: + arg_val = kwargs.pop(param_name) + except KeyError: + # We have no value for this parameter. It's fine though, + # if it has a default value, or it is an '*args'-like + # parameter, left alone by the processing of positional + # arguments. + if (not partial and param.kind != _VAR_POSITIONAL and + param.default is _empty): + raise TypeError('missing a required argument: {arg!r}'. \ + format(arg=param_name)) from None + + else: + if param.kind == _POSITIONAL_ONLY: + # This should never happen in case of a properly built + # Signature object (but let's have this check here + # to ensure correct behaviour just in case) + raise TypeError('{arg!r} parameter is positional only, ' + 'but was passed as a keyword'. \ + format(arg=param.name)) + + arguments[param_name] = arg_val + + if kwargs: + if kwargs_param is not None: + # Process our '**kwargs'-like parameter + arguments[kwargs_param.name] = kwargs + else: + raise TypeError( + 'got an unexpected keyword argument {arg!r}'.format( + arg=next(iter(kwargs)))) + + return self._bound_arguments_cls(self, arguments) + + def bind(*args, **kwargs): + """Get a BoundArguments object, that maps the passed `args` + and `kwargs` to the function's signature. Raises `TypeError` + if the passed arguments can not be bound. + """ + return args[0]._bind(args[1:], kwargs) + + def bind_partial(*args, **kwargs): + """Get a BoundArguments object, that partially maps the + passed `args` and `kwargs` to the function's signature. + Raises `TypeError` if the passed arguments can not be bound. + """ + return args[0]._bind(args[1:], kwargs, partial=True) + + def __reduce__(self): + return (type(self), + (tuple(self._parameters.values()),), + {'_return_annotation': self._return_annotation}) + + def __setstate__(self, state): + self._return_annotation = state['_return_annotation'] + + def __repr__(self): + return '<{} {}>'.format(self.__class__.__name__, self) + + def __str__(self): + result = [] + render_pos_only_separator = False + render_kw_only_separator = True + for param in self.parameters.values(): + formatted = str(param) + + kind = param.kind + + if kind == _POSITIONAL_ONLY: + render_pos_only_separator = True + elif render_pos_only_separator: + # It's not a positional-only parameter, and the flag + # is set to 'True' (there were pos-only params before.) + result.append('/') + render_pos_only_separator = False + + if kind == _VAR_POSITIONAL: + # OK, we have an '*args'-like parameter, so we won't need + # a '*' to separate keyword-only arguments + render_kw_only_separator = False + elif kind == _KEYWORD_ONLY and render_kw_only_separator: + # We have a keyword-only parameter to render and we haven't + # rendered an '*args'-like parameter before, so add a '*' + # separator to the parameters list ("foo(arg1, *, arg2)" case) + result.append('*') + # This condition should be only triggered once, so + # reset the flag + render_kw_only_separator = False + + result.append(formatted) + + if render_pos_only_separator: + # There were only positional-only parameters, hence the + # flag was not reset to 'False' + result.append('/') + + rendered = '({})'.format(', '.join(result)) + + if self.return_annotation is not _empty: + anno = formatannotation(self.return_annotation) + rendered += ' -> {}'.format(anno) + + return rendered + + +def signature(obj, *, follow_wrapped=True): + """Get a signature object for the passed callable.""" + return Signature.from_callable(obj, follow_wrapped=follow_wrapped) + + +def _main(): + """ Logic for inspecting an object given at command line """ + import argparse + import importlib + + parser = argparse.ArgumentParser() + parser.add_argument( + 'object', + help="The object to be analysed. " + "It supports the 'module:qualname' syntax") + parser.add_argument( + '-d', '--details', action='store_true', + help='Display info about the module rather than its source code') + + args = parser.parse_args() + + target = args.object + mod_name, has_attrs, attrs = target.partition(":") + try: + obj = module = importlib.import_module(mod_name) + except Exception as exc: + msg = "Failed to import {} ({}: {})".format(mod_name, + type(exc).__name__, + exc) + print(msg, file=sys.stderr) + exit(2) + + if has_attrs: + parts = attrs.split(".") + obj = module + for part in parts: + obj = getattr(obj, part) + + if module.__name__ in sys.builtin_module_names: + print("Can't get info for builtin modules.", file=sys.stderr) + exit(1) + + if args.details: + print('Target: {}'.format(target)) + print('Origin: {}'.format(getsourcefile(module))) + print('Cached: {}'.format(module.__cached__)) + if obj is module: + print('Loader: {}'.format(repr(module.__loader__))) + if hasattr(module, '__path__'): + print('Submodule search path: {}'.format(module.__path__)) + else: + try: + __, lineno = findsource(obj) + except Exception: + pass + else: + print('Line: {}'.format(lineno)) + + print('\n') + else: + print(getsource(obj)) + + +print (signature(m1.write)) \ No newline at end of file diff --git a/script/test/t.py b/script/test/t.py new file mode 100644 index 0000000..dd3f5bd --- /dev/null +++ b/script/test/t.py @@ -0,0 +1,7 @@ + +i=1 +j=time.sleep(1.0) +r1 = lscan(inp, (sin,out,), 0, 40, 100, 0.05, print_scan=True) +r1 = lscan(inp, (sin,out,), 0, 40, 100, 0.01) + +j=time.sleep(1.0) \ No newline at end of file diff --git a/script/test/test1.py b/script/test/test1.py index 2b296e0..968f7b3 100755 --- a/script/test/test1.py +++ b/script/test/test1.py @@ -14,11 +14,12 @@ Line Scan #set_exec_pars(name="out", open=False) -print get_exec_pars().path +print (get_exec_pars().path) + def before_pass(pass_num): - print "Starting pass: " , pass_num + print ("Starting pass: " , pass_num) def after_pass(pass_num): - print "Finished pass: " , pass_num + print ("Finished pass: " , pass_num) #set_exec_pars(layout="sf") diff --git a/script/test/test14.xml b/script/test/test14.xml index db1f4ec..fd6be6c 100755 --- a/script/test/test14.xml +++ b/script/test/test14.xml @@ -1,20 +1,22 @@ - alexandre.gobbo@psi.ch + alexandre.gobbo@psi.ch My first test - - + + 0.0 20.0 1.0 - + + + diff --git a/script/test/test2.xml b/script/test/test2.xml index 04e6b94..04aa43c 100755 --- a/script/test/test2.xml +++ b/script/test/test2.xml @@ -9,7 +9,7 @@ - 0.0 + 10.0 31.0 1.0 @@ -23,8 +23,8 @@ def process(): - 0.0 - 5.0 + 5.0 + 10.0 1.0 diff --git a/script/test/test29.py b/script/test/test29.py index 8045532..e119800 100755 --- a/script/test/test29.py +++ b/script/test/test29.py @@ -1,7 +1,7 @@ [py, px, pxy]=plot([None,None,None],["y","x", "xy"]) - +# #XY error plot pxy.setStyle(pxy.Style.ErrorXY) diff --git a/script/test/test2v.xml b/script/test/test2v.xml new file mode 100644 index 0000000..df9dc95 --- /dev/null +++ b/script/test/test2v.xml @@ -0,0 +1,46 @@ + + + + alexandre.gobbo@psi.ch + + + My second test + + + + + + + + + + + 0.0 + 0.0 + 1.0 + START_X + END_X + STEP_X + + + + + + + + + + 0.0 + 10.0 + 0.0 + START_Y + END_Y + STEP_Y + + + + + + diff --git a/script/test/test6v.xml b/script/test/test6v.xml new file mode 100644 index 0000000..d16a1ef --- /dev/null +++ b/script/test/test6v.xml @@ -0,0 +1,25 @@ + + + + + + + 100 + + + + + 100 + + + + + + + + + + + + diff --git a/script/test/test7.xml b/script/test/test7.xml index 0083083..94eba01 100755 --- a/script/test/test7.xml +++ b/script/test/test7.xml @@ -25,11 +25,6 @@ def process(x): - - - - - + diff --git a/script/test/test9.xml b/script/test/test8v.xml old mode 100755 new mode 100644 similarity index 72% rename from script/test/test9.xml rename to script/test/test8v.xml index cf97860..40c408b --- a/script/test/test9.xml +++ b/script/test/test8v.xml @@ -3,15 +3,21 @@ alexandre.gobbo@psi.ch - - My third test + + My first test + + + 0.0 31.0 1.0 + START + END + STEP + + + + 10.0 + 20.0 + 1.0 + + + + + + + + + + diff --git a/script/test/test_loop_caget.py b/script/test/test_loop_caget.py new file mode 100644 index 0000000..300822f --- /dev/null +++ b/script/test/test_loop_caget.py @@ -0,0 +1,11 @@ +add_device(ChannelDouble("c0", "TESTIOC:TESTSINUS:SinCalc"), True) +c0.monitored=True +show_panel(c0) + +for i in range(1000000): + c=ChannelDouble("channel", "TESTIOC:TESTSINUS:SinCalc") + c.initialize() + print i, c.read() + c.close() + time.sleep(0.001) + \ No newline at end of file diff --git a/script/test/test_manip.xml b/script/test/test_manip.xml new file mode 100644 index 0000000..ae35869 --- /dev/null +++ b/script/test/test_manip.xml @@ -0,0 +1,24 @@ + + + + + + + 5 + + + + + + + + + + + + + + + diff --git a/script/test/test_receive_sender.py b/script/test/test_receive_sender.py new file mode 100644 index 0000000..93548a7 --- /dev/null +++ b/script/test/test_receive_sender.py @@ -0,0 +1,19 @@ + +url="tcp://localhost:9999" + +st1 = Stream("st1", url, SocketType.PULL) +#st1.parent.config.headerReservingAllocator = True + +st1.initialize() +st1.start() +st1.waitCacheChange(60000) + + +#show_panel(st1) + + +try: + bscan (st1, 5, 5, save=False) +finally: + st1.close() + #p.close() \ No newline at end of file diff --git a/script/test/test_roi.py b/script/test/test_roi.py new file mode 100644 index 0000000..385f2b6 --- /dev/null +++ b/script/test/test_roi.py @@ -0,0 +1,20 @@ +r=show_panel(ca) +r.addDataSelection(Overlays.Rect(None, Point(70,50), Dimension(20,40))) + + + +ov = r.getDataSelection() +size =20 +ov.width = 40 +ov.height = 80 +r.refresh() + +ov.setPosition(Point(100,79)) +r.refresh() + +print ov.position.x,ov.position.x,ov.size.width, ov.size.height + + +r.addDataSelection(Overlays.Line(None, Point(20,50), Point(40,90))) +r.addDataSelection(Overlays.Crosshairs(None, Point(20,50), Dimension(-1,1))) +r.addDataSelection(Overlays.Crosshairs(None, Point(20,50), Dimension(1,-1))) \ No newline at end of file diff --git a/script/test/test_sender.py b/script/test/test_sender.py new file mode 100644 index 0000000..dd674e8 --- /dev/null +++ b/script/test/test_sender.py @@ -0,0 +1,77 @@ +from ch.psi.bsread import ScheduledSender +from ch.psi.bsread import SenderConfig +from ch.psi.bsread import DataChannel +from ch.psi.bsread.impl import StandardPulseIdProvider +from ch.psi.bsread.impl import StandardTimeProvider +from ch.psi.bsread.message import Timestamp +from ch.psi.bsread.message import ChannelConfig +from ch.psi.bsread.converter import MatlabByteConverter +from java.util.concurrent import TimeUnit +from ch.psi.bsread.message import Type as ChanlelType +from ch.psi.bsread.compression import Compression + +try: + sender.close() +except: + pass +try: + receiver.close() +except: + pass + +#address "tcp://*:9999" +#try: +pid_provider = StandardPulseIdProvider() +time_provider = StandardTimeProvider(); +converter = MatlabByteConverter() +sender = ScheduledSender(SenderConfig(SenderConfig.DEFAULT_ADDRESS,pid_provider,time_provider,converter)) + +class ScalarChannel(DataChannel): + def getValue(self, pulseId): + #print pulseId + return float(pulseId); + def getTime(self, pulseId): + return Timestamp(pulseId, 0L) +scalar_channel_config = ChannelConfig("Scalar", ChanlelType.Float64, 10, 0) +scalar_channel = ScalarChannel(scalar_channel_config) +sender.addSource(scalar_channel) + + +#Compression: bitshuffle_lz4 , none or lz4 +compression = Compression.none + +SIZE_ARRAY = 1200000 #54268 +#SIZE_ARRAY = 120 + + +class ArrayChannel(DataChannel): + def getValue(self, pulseId): + return Arr.indexesDouble(SIZE_ARRAY) + def getTime(self, pulseId): + return Timestamp(pulseId, 0L) + +array_channel_config = ChannelConfig("Array", ChanlelType.Float64, [SIZE_ARRAY/1000, 1000], 10, 0, ChannelConfig.DEFAULT_ENCODING, compression) +array_channel = ArrayChannel(array_channel_config) +sender.addSource(array_channel) + + +sender.connect(); +initialDelay = 20 +period = 20 +sender.sendAtFixedRate(initialDelay, period, TimeUnit.MILLISECONDS) +back=sender + +#run ("test/test_receive_sender") + + +receiver = Stream("st1", "tcp://localhost:9999", SocketType.PULL) +#st1.parent.config.headerReservingAllocator = True +receiver.initialize() +receiver.start() +show_panel(receiver) + + +#finally: +# #receiver.close(); +# sender.close(); +# pass \ No newline at end of file diff --git a/script/test/testbug.xml b/script/test/testbug.xml new file mode 100644 index 0000000..40b9f29 --- /dev/null +++ b/script/test/testbug.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + -8.0 + 0.0 + 0.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/script/test/testempty.xml b/script/test/testempty.xml new file mode 100644 index 0000000..5dbb75c --- /dev/null +++ b/script/test/testempty.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/script/test/testsettling.py b/script/test/testsettling.py new file mode 100644 index 0000000..c5e4b30 --- /dev/null +++ b/script/test/testsettling.py @@ -0,0 +1,9 @@ +cc =ChannelSettlingCondition("TESTIOC:TESTCALCOUT:Input", 0) + +positioner = ControlledVariable("positioner", "TESTIOC:TESTCALCOUT:Output", "TESTIOC:TESTSINUS:SinCalc") +positioner.getConfig().resolution = float('inf') +positioner.initialize() + +positioner.setSettlingCondition(ChannelSettlingCondition("TESTIOC:TESTCALCOUT:Input", 0)) +positioner.write(2.0) +positioner.read() \ No newline at end of file diff --git a/script/test/testx.xml b/script/test/testx.xml index 09d6938..3e49478 100755 --- a/script/test/testx.xml +++ b/script/test/testx.xml @@ -5,8 +5,12 @@ My first test - + + + + + 0.0 @@ -18,6 +22,9 @@ def process(): time.sleep(0.1) + + + diff --git a/script/test/test1.xml b/script/test/testy.xml old mode 100755 new mode 100644 similarity index 56% rename from script/test/test1.xml rename to script/test/testy.xml index 9dcb773..1c18587 --- a/script/test/test1.xml +++ b/script/test/testy.xml @@ -3,10 +3,22 @@ alexandre.gobbo@psi.ch - - My first test a + + My first test + + + + + + + + + + 0.0 @@ -15,12 +27,17 @@ +time.sleep(0.1) +print 2 + + + + diff --git a/script/test/x.xml b/script/test/x.xml new file mode 100644 index 0000000..7358f1e --- /dev/null +++ b/script/test/x.xml @@ -0,0 +1,23 @@ + + + + + + + + 0.0 + 0.0 + 0.0 + 0.0 + + + + + 0.0 + 0.0 + 0.0 + + + + + diff --git a/script/test/xxx.xml b/script/test/xxx.xml index ee504d4..cec85dd 100755 --- a/script/test/xxx.xml +++ b/script/test/xxx.xml @@ -1,4 +1,16 @@ - + + + + + 1000 + + + + + + + + diff --git a/script/test/y.xml b/script/test/y.xml new file mode 100644 index 0000000..9e5ebb4 --- /dev/null +++ b/script/test/y.xml @@ -0,0 +1,23 @@ + + + + + + + + 0.0 + 0.0 + 0.0 + 0.0 + + + + + 0.0 + 0.0 + 0.0 + + + + + diff --git a/script/test2.xml b/script/test2.xml index 68ba8f6..bf3f86f 100644 --- a/script/test2.xml +++ b/script/test2.xml @@ -1,5 +1,17 @@ - - - + + + + + + + 100 + + + + + + + + diff --git a/script/ttt.xml b/script/ttt.xml index ee504d4..f628750 100755 --- a/script/ttt.xml +++ b/script/ttt.xml @@ -1,4 +1,20 @@ - - + + + + + + + 10 + + + + + + + + + + diff --git a/script/www.xml b/script/www.xml index ee504d4..c7197bd 100755 --- a/script/www.xml +++ b/script/www.xml @@ -1,4 +1,5 @@ +

}L!?^%<*AriDDLlCLM&UtoVT&MmVj9IydADOHmkE>QC*5?Qjh2z)a!cd^K( zb8uf3m$V^W5GS29Ru4K$yP7LUXIZrTx!ZitVwP zc1S+pW)(j_fRDqkPABv=#$b+|eYQcC%2Q+$&&8h8nnG+zOBFK9(k)??5yt{ssw~`F z_kUkotjureHHGaC54v9IH$S1&xeZN!CSPSgHPflWo;{=~T5_z{rq!XmP}0MuIa;*p z(dK^Ipxaa``MB&`M4A!pV>RA`uUbO(5;27=PH#(%W0cam`q0iV@(d9u6;=ojTQ9kN z3m4$sY_~9zXsUo(`m##QDs>D< z^%6I$q|ks}#`BN^Zf)&tJ)+-AQ%l+i?s~xXOoE_ekqvLVLb8zYGhO-FUSZ;;j0t(@ z32RZmQs{cwSTG^4)&gdp?LcXP-#s=IcHEh@oRK5xb#>{TpSd=^S}hQ5O)Nt8R}LRt zwHMD!O$eMb9p%U9Qn-(le}P&))pU$A1+W(bo+VFxsQ9HX(H3+02VZm4HWs(LGe8bq z?$Io_F|2y7M3F0(_4W6sASl?hV%72(2-+y zerwcpfRCQr>P9-WGH~(8E*MPi@5HG@tbXY-4+$!4SIC1*6QW0kAX5+Aw6Uvv`X4|D zN{dRIuxAEJt^t`zQX6DC`+rKiyCt{fvQqq9|X zoVk8<)L)QQp{ix2-SK_l)?i<3v*<=3W(z_L2>DH47R*mc2`a@%=+0be6T|25Vqx>m zy#Ja8aCz9Itfb2kW@#`1)+k-rG9Ha-uhQOWDUq!S(Fl+ZNWksQZ>_)W*xfW_v3LS9 zUxqXARpcN#zVGkUn%3OH%yITgnF~218YJ_s(}2K04<*UL=?$W+(b0s~BkVjt+n#&a z!cNBo{W!RC;`x@t`$toX9#@-8^P}8^GhUC+vNunCIBE_3S%(Ztz4SQJ0t;LI`i1=M z6SlahSE+EYMS0I)?v#xkkTBV7*?WgjHkU^hE<2rrB^|}FD3Te3GfD}$BhRH*DdVM) zSJnCHrOEd}M=6_czjNI59;qxdLnra~&@cq2{!1-g>L`38IZ9T9RiX*Jaml_j&2NSm zypfp_d+hWVR#*#F`cW4!i1ui^;DI(j=$Rg2)Zh#%21;tu{bx>|fXDPxnS%LVQ&GR& z@7yC5uKR$ovd8@i!*_x<=n7SJd+O0wB}S1Ib&N6R4tVJ^5gCEu`BJa zTi9(^*#nAu`o{(wJ{H@*a9k29D{7a7@WD=*Cah{&OvC=1inS7aJ#sG5qyXsi_j-a& zd+(nFDNnoQ#&aq%K5+4l6nUEVVp>os7Eu|v>>!OZz~cjAg1oFtO>TEwAp|d<;JB$! zyQxyRP@3ij>FY-L`9=T6LNA(zBL0T`ZGOYN+^Lk8hWc zG;?_GHx&=kF)*XLFkU7_v*-H!&^`>&Rt-9bI_I+Z0WVmcp1!=ZkZ2Z=d)8EVbA;OD zRAFCu`zfA31hI_g)#9Itrp`2dG&3;RpqxO1v}A=`1xLg^enTKjXg#|$wd2k#)O?oLn$3QnW# z&Ni$9JebX~ZZxE47S&lFXaGVe>(n6gCZeum9hzOsho8BQpT7x90&nmQJ z2!)56Z8w)FPs1O0F2eITlw{qIstt-e7{uI#4aA|PeXsQ0TO}^xa}MQKE&HzzwF5%? zhEvSEZX83s(x{uBu4Hc5qrEjd2d)7O$t)BD`0?9c0%e}|?(Yr-;at<0g=#aRUA78f zU>UseUt04bL`6JSKO^jNHA;)qmvlS#)sYkr9b%kr@12RV^+9r+F0-*80jk*`kv5() zITqiYg^N~zbBs5(e*s);+*4(9$87J~eJ`Thc&ti9vDMayzhhyu2r`G`9mwt^a?7Q+ zzB0vq*x)*Zc_s|%_It1Lw9 z_N&8|dk)0(JuQX=OX?`lF>;clKkU1f`HLp=aB7e!ws3?6kTF}usy0L{6x8$-nMJh17zI~WyD(EIaMF<#-s&xTZJIp-pkIb+B2ilZXo4k(cITpb z?lJ@*GH;@o)*UHg9?P|_2ghv%NRQR_bXoVG=b|p+GuE&W2TRo&G}o{<;9Q0)8-9k_ zZpcWJAU~60$}u+lm+K3`yW=>^ouuQ%ZH+NxdZv>z!^T*46DnXud(&@y0LSj~q-rA# zgF3r}K8s|G{KHD=$VmIn(66*?}0u+}(3sIYm@9vQ6m{@=;A{@#N_6d2FIt(VZlt#)O zhW{GCDVhOncUK3vlX+p{5Cb6VTwcUkQ^w7&OpF6|)jFxMf_bRlNofsb2BhGHV*MBz zmS-N@_r`fUo&7yNVEQQzt9%xZzRKjwxatR4o~`#^&CdrI^%!exOM3octR|Fv@of1@ z8@IN?Mp<_C_xgJe*}RO>f((zEU}pNBA%eD~sdQ@j6zr;+9U9x!jzN9d$e*|luer_rS$I5MDV2J<@EY;`-MAm2KkFCT8i6m?|FlqsWjuQMdF6c4 zOG@Jj4jTvimc*Y<#DY1KypodR0;t-OW5X*D%$!5@L(+FMclFMqOzhv*>^garOHGzK zH3{n@eUBoWQf~i=rW8rjt9sAxp9oP3Ca^4o`{Q?(qaJ0puqhn_p%0h`FU2Q3`{I2^l}uvPo`1OIN^zh?!}yWWWsU z!F|YCB`Fz9&b2!JA*;SkuaK}^^=LJ=Pp;Rv0WE%ArD1vNxk0Qg^Enxw{Or&MkawS@ zV#og}v%%uG2u9QjlS0MJj4o7LN*L$%45On2-*Y2w$VJo8m=JaP#^UZu^RQ>!aRZxZ z`B-`;aU^Ls72^Z}?EQDZ8`gy<62}961)s^bm}(=Z`tS|8ID$#O)D|pil_Wn?G3oHn z$xV>k-()v-E~$Za^KMC$qx`Dwzp5ruD|aD~3q&%do}Kqh093x$6pe@2RlsK={t_CJ z36&z3o?<)6-T%TU?QvSB;J^K@TsHujL5>i0xI*C5(efT05W||-_4t3VG?b|| zJ>9AJ8c}f{>(C5!C>oCFYX*zc1iQKYUOuP|7q&mn$(}m;# z1DMT;`@j$kNHK_KlvHzhdy9lQF|55V!(CgOe8cDyat$>I#=bj{y!BfQJrlN2L^63@ z;Q$0k#_A9mO|3~cCYS2GNbdZpkDaPZblI`Nvc&$&(u4JS_7jDlulh`UV!?pXv|6ex zzt1SqsWSJPW{GLRuh>G~Lsk<#;w5Wr>t$zo5zKVGXuf(=b9;5mPo&liQ@SYcnHM0< zBnibSAHM`h1u5Zfb1f>SlN3;}duO@wLiCjM7dk8!$TYoa;0xz;06HyFRe&LQy>hO- z^Tr`T?eds%r3%oN8|n6wwpQZ4)-_ix{fUjgFcV>^i{E#bM$l~?k zcj<4d^aMf$NL@LoQ1S`xvITq-xKFq+vHzBETll?gOaKjaMgRe01_EMUW+~(ctDy zW@=6H!!yQ{Gnp7u?L-~(h+0XH&jkAxZqMHT^@hzd1;=8MMISGFuuFM6E;o+7#E3Q; z(Ixb_2J7223;%G~y5lUIsYt*Y7nYp2(E6j0W)6Q159hytQ_3;86KF8XHyV z9ICp9PPjE)AnbqV!kp;z`3zNlRZil3wNFf*`P%fq#B38-WhMP~oQ1PmW^?$X6=s*Y zv6tyD(O!OIcdc;1hVW7sTPz}-%dK!I1bOaaCI67CB?u4NgLQu%#(LLn+PeR2I;rA5 zmrHJwMc7{a-zbl`X@Qk`TAonuGQj|F1SrCMv3_%ba{~weOV_bx72_-Gqm?a@QnN}E zAmf!j2t;uGh&3{T-0#@7*L8@Uv$gg0ah`UCYrSM<)8gIDfp)f*P1jr5WFWR;dzA`L zc;+7IJu(F)$H#=il{UiuiXz7OEbqD3UAVLXy9|7P0k%T)!45$Q01Z#Z_Hol z5jo{!XS(9v-;LD5zQBfdyU-Q)R{3GaC!rqwP?N6e01(p(7b}FV%a}sUY*X>MB`-5A zYqhWU5Avv6_`hnYXXjs;w7)Weq7V}cM$TPSP7l?<6h}1Z)x>q|<7*kOE*N|DJh!Y( z=*!M2t*Y>GEf*?j5)f9Tj>^xBR8EAX2kuS@UeDA~Z%^ae^NQwFPZZ(WXKT|gbK!rX z8r^gt4f1X*?rd0u@MR76)g+h&m!pV9FGd>?JK(Lb8J3{7SLcbIbG^92xuK1WYolPu z-8DAHVzUg>fCc=BSA-{Z|NFs#RITykpGQ5PyVr&D*KW1)A3TH+Ru2U-(sfLy*=|g} z%7kqyrMabps^i!>7&-agktt1Ui>X@aX~)h?3qUgN(#R-XTB;*R4hb2*)q{AF>6D6l zmo}L*0IgI!LUu-CbgFWTaSbtryABnl{0iY@=nm?TLD>lsi1VeUL{nFI<&ITRYiz*c zg&)p{ho-xwYqL2_1WMyItOx{STW+bz@=_hy#IYQfHs2lC!Anl)20LsOiq!LJ6@X9v z2aM9dt(Nq$L7J9+Lhiq#w{*0k(LNKl>Wja;pdY(XCIth;en`Lu=e(Esmyq2x>33ge z8AZ*+F9}^bd4gQ^a^eNEN?BEbJ*$IhX|GFUC2M3!mt>aOt)j$7_+B}5_#XYg(%Pxf?Yf^O43^Al#ymKqiS2<*9pY;rSjRjz<^1YQPZEqx zpd&OH%*p3d<+x$9?qrJnJ+%Sgbzlc>zziWT*`xSi-+ly3I>kPNs zSnrT^??E0_os**3ouiA+)hHp4x!h9h-(}L;LllS?ufWR0JK6uBhfICH-e(c}xhh}pSN zBb@DfvMHhTO>(;huwid|B|e{r#heGq+p6eqWCQYY+jd0y<7$Sl3ov^16pt%hF5g=R z`S}ps8`9cq1Vyj4O12;f8FHd*SoCLF)~t8ooHUPEc&@^T|(GC9I?!2urfHd=J# z1SE~I-#VK61RoD$4FQvQ_g@>eibQey6c|`T6TaC%)@JdJ*T=KU_Cn6?4EoRE+fsm| z{csve<|J~Y-ASS$Jaz{d8X%B;ibu^%bCa&_zqG&B(pJ0N#9q8^Ha(>-&9x(ztjCJoJ+)G={@u^N zhr%DRyPD|@Ki5vW0?h3fMBE^axVf6`9&V5?LO~{!^%#;FrFcNfTMxxoa_WciI+z>t zS=T=%UVIPd&Yh@5eGCq~-4N}rA^{j=krLY`> z-(DTLj|yEf$WA>yTl6Stj6Ba-^SgV#b zmIvAs%J==z!x!;t?o+AiwZZq{tbJJFsh;=t zS)=q6j2E+?L@Eu=4&_y%P>`ibd+hZC?yc|95xYy_kYgr&`IcC>zi$Des^Z3Df<4st z>G|OO6&wg0ox@J>a4`uGfP98y0;iRcjm;(@znf(i8CS5j*6n8^%bR z#0HwgyQ@+o29uPyxR-ozrU64Pm6Xz<*spa+2%i5!6n<%4tS)PDYv65Hbj8NC6O~Q{v@=_=p^~An+nLbXGTXjv0pl`K2)=5=S#alV4ms9w02$k7Gc@;=V6wq&#G|@ zoNAoGm;ILB3-~&^thfJ*F4wg?3%^X^miLEx9jc>@{FoE55L>2b+TE$;&#!S{0->0m z!R1)b==RKut+KA}$GdC!1Wy0+e$!6{Ja_82VtF;4=#`qKwYQvB^=Oy(+bgHm2n5oHwyu2=lX}5?o~ac5$qz??qM1H>dX`o0?azG* zyI&*Q^D6y^sx9Q!42T0oaRWs;Z=FZwlIiqjI(fGMyen#7r@|fAG7@Nh9ff%n*)2vj zv9C>lYwUkqUoT+7t`KNlCFyiCMj^xPqw3u;mBOhJrwP;TA$G^#c^CR~$ zy^4%jsX1W&4K6b!cGxFfnC@5gBgfQ$QXiKWm!>?zfBcJEK49=g5m73?1=vlbl%d;D zg_q=^>FLrD5OO7?=P#mA^gEJZae~)e>>F#Wa>LK4seHpz`0E!Mv3|^> zJ$~y0ORRB`(;=n3A-4u>XX$apD*rG>!OjjV7Lzutk4HuVpckwiwsw?{ZjJJGJT@m^ zq_eUNmXN<3AfQF(oFNpKeo)oY|3+OuPM$7{%hWmkjN9+-tDk!o9F@bFFPIhn#%^2_ zc-y67hO9-5ZFv#{#B?ImWWD_$#q9PSQ;40cPSEB#lmr+I=IJv6MCqWftc(lvb??5` znvL91U~fnh34{IE}pUNgm3VKrqM9uyX^u%)J_Qr zCQ^e$jmz3Nfid5q$M(E#b!WzAL?xwgc%ODY=`c9kLtdQ$mdsZ^^Ag|6mP}Wim|Lb3 zyp}`hw?@uv={SwL9ltMkaMtxa8)LwI4;o(M;o4GjA9m|0qSja_WCrJH#g6V>Bl zS;bXtwEKQque(rMZ~_G3nVX0Sk5s z0oIG}G^ae}L~AxDw30mC_;j=;+q8H6E-KA!edLcN$2Aak78=-~Bu6=r1?aI)whDUm z8UvSJN=KSlrPh95FF6-2)R~j0JuQbV#R7c?|DDk7rzi0SO$frKL{lkf-|uW_G?h#= z2{xyA=oC5;h3WcnveXpdwcR__$xp+X?d=MFYDqP{?;OV`ZF{JSWu$^vanIg;eZa*R zo_Qcl(=CVgSl*Dv@~NoCQ${>B;Xl2rd^48^OwF+U3d2wRR{9oxyu?cxE%$x`Fq@1S zkZ&2Vv%K<`4<=yoSeTpkoxb66Ld|*!rDn(J&^uE`w#N@S%}3t-zxJ;DsmW|j2T;0{ zi;QCngDBpXZ3K}k2s9`JhsKS~ZUmbpAj;Ne!43foVe9MAwi`4ALBNnGB0_{{*n|i{ zWib*V2mz7+E<_@M7y$`J0=eh=5~pTv)%_3VtKx@L!O1!A^1RP;IOqME)T=)0<=39Z zK@nvQdkXW$$9*Z!6>bA*U!h|-gd^k?Gz6jlIeUr7*C}{oIQ}=eNbZ_l7`G6q)(Z#NG?&5 zVW(a|a_9`99?eORHY0YTR_iuF#%`qh)0|&W!l&kY$sh(c+s*0_A_40%K^0yxttf{# zKa92OllJNv6)LA($>F#MYeAI)grB>IBk?TWO2Xuudth|2*cYYZ)|uykJ>z?}@vpWZ zC|!Sk7d*85#z;esXlu0v?+an3Hnz{hRk=XL>hSU*h!Ot6iARz`z9`x{|9Z^Ac=xWP zuFV|akcBCM-uux24`*eU!zE1PH8_=NIbR3jkKKs))u%+B=}Me3B_W3B2hP`I%Nhou zrZ>y$hL}vh53TgpTA2_{=F_pdgsot4lRY+3UkVoB_sKT=yEYuvrKJVXYgj6_Tlp3P zJDEd`2MIqp#`)o)Jl=xQU7UJXOhz+mpES88)$j~yk%p!P5lQ~ceoqY%O7PJzCfejV zJ970`W_pVU;>-Z3`|;%^ARKN7ZYOyin)^H%Tla7WkmE4}J4lGdcU&j(+$6;x z{%b>R>B@~)&OZj+kjni$7yMe^Ygszt01&Xoq-$wx5NpHBcXqX@kGs~Qnup-C5slRy zs%y_6Uv>xxaOWstZ6y3d11s+z7TGUHcju`?0EZM;7SHPNilF0=xqj`RyTsQ|L*;kV zdrZi^!`A*aILz|6d{$?uC@}q2t3SRNrBPD6-?f0!(|jfvKV2EyheX0AZ@A;3I2_ENRLy;ex zri(@aJZqp$yURnsWvNb=%uSM`)b5~$m?PvdA}o2Jf?VGp;HipkE=AZ+R#!a-3>%~; z=MhnX2h{pfG+$$F(CUo_!sSN{HDXqd9UT6t@)}+q&0Pw zkpE#EtJ{z}H5H?nbu5l5k4Qx^(;EG=?FSpt^NH%BbUCSSHyoJW8oMJB8*nJRtT8ro z?s81uRuX~&G>1(UwfqJVk_#fg?54%}UVaPa$$yLc_R)EUug6Cd+2UfhpF-5HgbjA) zipF3kgUr+KbGuAz020}SjOxAL3W6cm?M`cs42p&6Q=4cj9xH?rIwJre`Rcq036TX| zd|YBvRlV$Qr-giTcxLCdGn2VXU|^vB2Eyfa$5`g) zXx!ivR_rgq%!GW8?B}mmQ1}5K4vyv41DncllLpN(xyps!2x2V$-8I;_Qs`RpIvdL< z)hT@EMu4K3ddat2+*)Sp95>lmvz7iJz5HoTTfYCBQmzgNaO*k%dr_LLU8iqhEPiPm zPPNr9Anq}`LwOr53V=8Y3fxvKYu=9jH`<5V@M0imN`S`!6OqQDXs6K_zxKJS;(f1n zGWj72I2qkFN*r;H5lO5}Bc{E)4V6f~Z5g7mDvB*mqWT6PJxpD=FRZ+J_(O$$Wg$6O zr#MUWh;WU2X<&w}Dbmx_v9m-oJ*^vPR<)r)ZTmfb`Dt;U5JdLRRt%0_zp^EGEym)I zF)*^VmxPIrb&Nfl>;myXKxvNk>l+1R&XdJ*bz~ips$n7>}+=2nCXpx*P#g^nNtXPUqY{yO86}(~+qt{K5;|U``mn zjWo|^fB{oAIIaO!ZIsB~yt&XjMhJqgw8!U`9RQ*Jf0v6WqgU?)gvJ(A=IIgI^3s|& zacPIChdXo%OW(+NTBByP#V8@sfqjE}BQ&o2idZ2c6BjVLm@sc|{Il;kJF6@!4(0VK zM0w8Zy$nK|>)ea|DW!g8MY|#~n-b7H+lo~^S(Z;PTpoqJpr_diMhulo%I%!up_@R9 ze~7XzP2KlG;=~~8tT!MxUTw;dJ!3QVK`~%M~+eEpXr|}`rtR$@qW|P zB*d9kNf`;I59nOJQ`GlmFU8oFe9 z5W34!PVBb6@3v)2!DL<_)CKVS4x(nfjbFzrL$bLZOO{Jxkx^bybSSRw=twawPe6QyIxCzx?p{a+_e58Jyf4grBpzUfUN!e-l~H5*|spsf2p z9V;QpU(Do-&Kp^qxs2+c@%a&0*oLa~+x$S*QdQid<9-P!WR|?{ZBsa|x-Xh1z}EXi zO^#gK^JiP>NpROS!YNG!&F(u1n8fOG??_@IYiU9}*CW%#R>${?pIMN9tSC60=bs|z zx;%!UJg)CAf_h4U2mrFHirTb}(!;yS8EG%NkrD=U)GRzjO+iJt*Iu)un~ec5YTW}J z?_#U0LYufQyq{7gTGo#oym~ZuKl3mn5=fd^h<<_~juy=dIJbOUZl0cgS?Ruc5uB4`ZK6D?pPG{T%&=9J>qVfJrkQ8$C(ijS z^IBwWshb(wG6)bMNn2`KhX}_#{<~x9D~d|4woa0SxJq!NCr!=-F(LtbwFZP^Yv#j0 zw!@P=q%kaFqAa#I^Lo#Ab%=RZ`svpS;lg~c;^w63-v5N&V|KTqCL7yK zrLW9ehE6!F5ZWn6dkVZaRPSg)rEFyA@Y;>-ZJknao14ay0_P=28+wQ!XLzvE7eIV) zN6M3R@s)|Me6PT}n3#2%xJAfLFG^#;Vhv2D?4j0+Z@VtaqX&o66rj2}xP9VDf#9vE zA5W?Oi=G$69Lmq#;!;-&$Fj2KtB}F{Z@ZeJdqKd>a{{d^4qEkWb#mJ``!^x{2HFmi zR^HQGKq=T2@o-NHNm?yk5X93Z(^ceacCyYQP!SXY8|AY@E}kS=@NM)75rVg8S7b!H zc~hHli^tp#o7sv;1o#35&071-H2b~IR=!1rp^qM38jDQdL!7R>N-awpuB%n6dL+algAorwdN)g!x%nVpe3+lhL z$eQT5Ad-Cu@T_YlDy9sp^3+9?F#@6P}~tpE8pv_AOyBqVFC@%0I#XrSkRh6u6w{~`YM z5GzQB{e-prnjh<-&qsU$eyIDelYjpQP)h>@3IG5A2ml0DFj4<=a1Q_7`H4_YM-2)Z3IG5A z4M|8uQUCw|WB>pFG6)6$0062re!c(z00v@9M??Vs0RI60puMM)00009a7bBm001r{ z001r{0eGc9b^rhX2XskIMF-*r8w?l%`VTb*000JtNkl^T zu$S((Ti3N++pXR07E8O1jBVWz2`(&>5KNpnBz~|OG0GB0@Fhw#AYcgkVJ46mOs0t% zV@OP3L!u%>62&bVc^esSQ@YKLL}BeG6_H5fYY9k#=-RbwR=He$QJO0fM+BfuijV|SX=!Pe zG%gb>LVU^G09nHJtljQKT3Qu4JIz?S^Z^VGvf#M$xP19Ks;dXk(cuRG==Ffj23)_6 zj5B90J)_aA*wfPkc)dLO`ijxtFN4jdM|XEF-0oa-cIp8Dq8P@NE2l6r@(rBMkC{`a zE=c3~?%^#Eu#fcmzX+gM?PZtC|3*$u->i8>@#g4HqU3p#l2Rm^>@n3n)-Q_>Q8bw)=^$w_q0CJ)A!Nt-CU|ihA-+!;Q3xkOZ$qH z%Fvx@y`p&aJ%A!wZSP&rsnws7%_fjew`EozRaM8Ry`4!-7*^|}F;7n%N3?9&mt-=v z63Ygr&BwAtCQ}wn=KBqF(>f@;cqp?wiod-OX?fbwFA}dKm!Z{l-43k-2$Keol^c zd3ia_LbHjGkbEGg^IAo^m5s);XHJ%1haw=@jSPxvYCmrGZf7EVclbETr5t7U0JylU z=h_mc^uW}aeqPQB zSrO}s(RJ-B+HXxq^*9ox&-(iM4*5td>F!{i4GaueTWx>qR^lP*eN;O={cZpFbEbU@ zLmY>2bJe&B8V;e-Y3oC@7YvXuD3Fe}HUeCsO~QA>%azOm9gqZ+zROzda`q43Kr&h( zq*J>eFfkX-KC|_UBa^U5RH8y|1yVK@eztCwZ?i-S67#- z$!Nao`0VC`k0w|8mu%O8kMVwb*^7U$JDzQUg_H0`_1wRrYYTSW%-&?IX8AeQoWO500B(Wm&LGRm|8tWQL62_nMctRxy*OZ>@U! zr3|~_`w>pAGTjP{MECi+yS8Rtuf?Et)vs}(amJDg7yTrs-|&b1M^~-&scK%XJ3*T+ zD)+XwI%f9ILi!9S@9a}w$66Q6d3h;?CjS}Wc{Fj4HJfbG`IRYiFnvg82v zwG%En(NfZ3K|(@taS4p3E24IG&3Tm%REF$TRiwI6(%w=5AJ)ZBs;Fp69>pWdIhlu= zED^2DbgMkHar0Z#yf4wwSAX2<==ohb%8KWX{93rU&w0&wp61Die)_?MAo%{hYDmZ1 z7cZi}5&BBA32Rs!mfBI>cY=~l3vt?thK}<=@RoKlN}@J?JX0ypUyQOyEs8WPOA*5_ zBKD#1SYBbHMa7Oc8>JxVc2JY!p$Lt^Y}Vo6E?~GpTPfz_(|zfHiss!bz2YJ~3 zqB&7qN+cuW4$5>UA_F!4j;@H)+e~+HeHrDXr41wr;}Qwes;YD^eb0=f*}vZE%ExCh z#6aY1m_D@4%>St-kvx3qW=HQjiEr#0ZG2Yp7Y01s+{~yOZXMRk%f_#?_pbNKRBKaF zABA!g8IZl_nbCAJbd0GV$)ihPujhYfm+}rHtAN)pRpet#?HSRl$~(a#f)qN!1_qDr z1jz*i{Ak+Qe-r6jn|5>e>fTk)I|ZU*q(G5^hzUsx<_l=O09Vh)-*2fPUxh$4fiG0x zU6w}XOt2bR{nUWvwnw?ls+@~r;H@XdPXi*x^QyMGZYZhH!O#MuK>xtV|uFY-cmIhIoCr zrE3}SmiEW3a1KP>vyuCsHy>wP9CGgl=+A9SrshE~m$2r-Q?<5;^Jxv=WtsOKw7+@q zX89}6Bnr;ZJhzDs&k%#@K80Djn}hm#`)XZ8>RZ`e*P2Lo8o8T3F8K5}O7@H;?ybvY zBxgx%{%AdGadW(}{k?`tCCnH9$UOhPOxUoC3bcpostjrrM9FvcbUtbHRu3v;S6=2g z^ERzvuea(p$}QzxGA;YY1%@GC!~lL6#jLp&18pnJa!F{$2a4R_iFrir=!fvg;nqID`ld^QqnHnVq*HC8c~0LvC&P! z$81b3v&#@ynKZ9Bi8M7>k1dMjYD6Dv6eBI(?5*;0!J{GEmDt)hR+2eI`@0g1^UO7> zi@e{S;JcHXuDJ9OW-?Sw7q>`dtfa&?r)MWm&6T_R3skHNEZitoSE;#uQ8UBXv?bjk^QGv!${4nH>I>;zW#%>`W_w;vyExl9 z<)~Hw54vT328CvFE}q9y_n1wj`nl(7 zKgMDHr*=kSF5eSl6wZlIANG}8tVXC$W6;CunEbgQE4gI!s_RyIwxOHXYsY4`-pMH0 zf8gk~XiyQbHxa8EncZ{Yn>Kmp-%I|H?=yEOm4hs5um+-2^gdx=#fpFh~r`@t#8y>aut{>8q9Srk%$WhBG72CYR;5%9^a zMK-yLnb;gg(#mX=a9wS?0%B}d(Pq;oGlnp|`I6#|>^oEw4;J-KZB^2B(lfMD_eZa! znr=^D8w?C%--z{LxD+F$vqWnmitMa$`dE?P$exrwliIt)m~6?0xK_?wA^tYSyMT_6 zut9;QhxaGyq;(S?jY8hxcICvMX2>Cjbr>>6Y8{toD%_rHiAeFd?n!C&1j9L$%hg+b z&^w}T3=?%=Bk9pip~bisUsT+ViEM%w+x`0s;`UuP!xoG~1en#i9;uPjeh8bt^JPZC zNM{n8%;uqBPFTt`c8A!gGdJ;G^Mk~CgU=%w2eZOoPqYs#6o#T-Eu+4J;OV#O25qWH zSaH^hBvD9wqfg%MzuRLq%{nzTe6=JvjCOMJ*cW=I34hH=f0!{vUvl@IE~a(Cyh56J zp@~vd(c%w1F)Q>!16c=gk0K~9e!JOcYO6g&V5=h2`?gU{&-f-*)>!OJiB@vs0;JPf zvLpSY&B|LpB=p9Ws|kMP-)O%@Rz9*8R$LW-985L7OG{Ah-&RnSMuWS$#D$PiLB->8R@B-WiB83kp{nw|=faUHq#u zdC~3nB;0i5i#^{^FV8z7a0Uc~nSrk7+MQF$6JVWLx*mqzZISm3B{BRovi7 z>5|2XsSi@pjETeOqx&}Q%hY@*1bDsvs_PHE_2N73=y{M4X|$C%&On^5IRbZ_=b0$D z=r_@UN0E0AvY}Kw>Y3dk8>DOroL_4kr*A;#6*l0Z6qpzcq)78Zr8Yh8$b7x#elq6U zm?+p7o50OdLTkS+A4NC5D0a-LTxH#tn?)LsC%YbqvHldzp{~+}{>8-U-mFos-fII) z_N=$^xcB3(25V|wDU_xP?xtwx7!1X1B)N_oiLIf$T^Oi;`+2oQFtuiml;&gKhlgus z`g?EuvQN75j~RiFNqmLWVwLhW+~up%2j!J(DOajxa(j|Q`J!AF}i(f5f81m z(nLtD^$$b5fr8dL4l` zYQe9Sd(`6^_N6qDa26ulToOgAGX{v4vttef{g6JE^7L=H(nkABO4!Ct?wA}+ksu6W zWwhw^5z0J#sT&PNdD0p+<^bu~Od-8Hs`YYm@me&Z@_WKwA5E;_U8az14prVc6tpZ} zQC8i2j!y-=kBAA%2vj7Ah@B7bN4JqS?^U@8XNOYy$BkA7-MNv@u8xmhV0g94n49cN zaRY6uqdHFM8B&%j&F$wWSsaovWSa6M<1=WTOVh*oyv$){O0I`U*mWZhSKJB=38wYN$hQnHq^SI+XiGI4mZpkO<0!pLY#)9dFMgEZZ-Z zafKOe%Yu%WEH1OSnnBE(2Ll3_Qg%jXeVdFn z_ag$(MIewNcTBrdT5ZGU{L!Dv5El627RG4hQf#l$=NY1}vXMzL>pG4-Av9~eyt#C9 zXlHN`LPr;6aYvxMW}s%XR={;yl!bDe$5e#UQFgcTvdi*5Lj3`+A&)P%&RuAFe9S*v^OZ_uE*LBFfk1p7-*Ui67sgR z4$2Q#Z=j(Yo34GgC3?TkS>V&~!t0g9v2paOyioX!A6^_0$UD5>7zu)yY##SesIy}} z?8PQDQrzRII#H_7SkjexG8{LBb8=NQZJ?A_Ts+>+Sj*#EbJMr+IP>u80E&BFK|!5Y zUMj1JzSGSapX+|rM{kXU_QoNs8#hA!UJb;NS8XPJ<8|654x*!whG>n3(X~;7IP3SN zQCF{ELl~rrjOD0}D3O;&#$xl#N}|r7{fi*89heZ zR-IX2|B3w$9cf!`N{V*Nh$NL)%eeT4EJ9Mp}FkBKJ}oH{SAg=&N|qZyDfR7dP*;kkDp`N!P4RxWuy7 z95taIT&>A1`i>za#ed>ke)yxt+1SeYWW1Me>z<0BZ)1B7zLv6?-+hGlpjr4^+tH)Z zCbZS^hx=wGwH6bQ-A5y@Qa9q>tF11L`%zMMT9q$1H8uGL^qj z5txx;;>;zwpODOQc)yiT$}YoV+4Aj6L3&z-wEn>H zH9pnV`ws8N3-sU~+nkl;(m&E;G6oy_0ocowE6e{<& ziMx7Kpm&=N6xeKl!?wtU`HcoPIT~1jy({5A6cy5|CM_naj_O20_m_k!Y6r15(sd7=?DbdUO!T?))yRgk6R?xmAG5`a z)~<=I%@b8nCJZUxJ_)X{_OsTf#Z8d8{ORUui!xeV&RJb%r*Iyv!_jB4)U3BTIXQ*u zLF7uty5TNiJ3oWi*uNn6=3EMIl4R~?-!4$U##3a=<@JQ zz#-y~TsF(ZJf-8$CMFR{<$r#nKckMtM| zPFB+K$*WZIf1E`hwcQMIFxK3X3QgHBYGP-qKUDB;mQLuXTOFJld=ZuKR^QVpJqV{h zrPYVh5O>q>nMGfU{OgSyH`xtitrDKfKU@Erw^N6MTt^bawagq9&im2F$3N}HNQ^ud zvIO~xEB`Y$^2MuDZo5;b-5g~FP39K2OimM>-AU+j<%XEYs7aliAQ0b5ea_weH-cyQ z;HC1Y^up5H9JB`2^{N>&J(dz1z6qBI4c1j+B^oRm3oa8#olE6`|KsoE zp0B^e6JJB|R%B9im9VFxAEiLHCm`chEw}i-^5r@W0bzO9(Whbunayq~=DkWORAA@% zjn<;uKyn`GVG{>gpX1_tP4y$A9y%q3WS0EV;6!$Bo3C9KT3mLXRt-Wa->Hsl%V}6U zXmr|KTw3I#!T~i^Q_RYKvoHsl#3R>ifKCj7v0rI4e!7YRU~Op)GQzgHC&@Ej}JF80WXi1kQ;IlwTX#LQ@i ze|6sRq#<*JKT(g;wzXrnr0izZ>{REWt$1#2iiQi0QU3aLQNCLjj^f&i6$(p2G$Cq# zhJxwfv!N?P*Ybv__CkyY=ozdYTh`08u?MbL23YX8#!y0|Ws6)N2_*F7HdCtgC6kUt z*vPpvj$-DvQ@XHqJvs~?GIe2Mz(09h`K)T>ME9lC76Bn$^Uj6U>yfzddP2EtwQ4IYO`_b+(wh5nONFdG`Z$TbqG+VPTqc z?dxHa%C*9AR93&h@Es$r(A%1%>KFtE0upU8rY?P0t)Iqdk-kPXRRX>ahy@OxanU9tXr#_P1SC1CHeDjW6xtdGF)HBK(nbz2g)~-#aCG|on zoZC;m5ax&sd~l2u7Z_;+a6GMFK8(O+eL{ZE_>CXtZumzi-ph{CXg~I0!9KKff`e|l2Y;SJ*@ht$$}+d;hcRHIj4 z963@)`R^{yY;|z}pFN~kUtQNv^n#$7JPz>*uZjK&sx34NT z7u+q}zZ*}2RR<}bb=0mYQuZdec{p9^VzK4gipi(qymEw)C|~hau127YW$uQ(BoeWk z&5wg2CcYeFs0H2&&)UMfHzKrO5mU`Lm<1X1JzN35XxzDi*>}BtheiDA!Q~c>;gk#(mO0xE@ z@*LqxQd5GX3hzSfP>#4sYlWc$sK|xqbKDYFTt} ziDj<3J!O--Vl9X3OgoZ>cWTp>G%q$fgty6HP46rl&+j!d>_nL+)ISp~`_78_NyIj# z#I8 zxrTq+roC^c{V8*r>oxlgmWNBt-~5=Bv&^W0&v5cKWqYy}ZmN{zETc7T#b&#Cncxcw zj`!!|%&@nCZR)SOzoQ9RC!vbDDbmT(@bk<_zHhTnX%Qq^70c-=F8n@+@6ZT+n#`y8 zRz4PY`&dAuW4t_0WuN%fV&^#TLANv`Q^5ej#3Mz_3=#6%yChb&p9qG(9IeL=^c`KJ z(aMtWeA_EfWHA}fna?&SE zk$O9mg2KfLMiq@^57kDluT&eG^!rcZxo|c}C>)0$zI*@8g3~Rlj3_`~azWLak)Uy|wMnL2g%&s?nyIiz~5rez5G&jhXVidvBU+Z=2c4O-wNVVfm!6z%xxa z_C=3m(!0iirm>0^mQj?>*|f~V7^W}Ycc;+gkJ+C$%Ge|b@PzKxO;U5c>=6C1aLKxP z?x-+g;HaRXlxB~oVt(1G)>4voKxup_0(%`7l#6m``*{IQde~+DXzxO*~2UY^> zN`KXac#zc|THr8GPlK6>tOZ)gQeCq3(CjHArPh@Zj4HY|loP1dsbH8+Y*OvlFeSXw z=1W*8rC;WAMif!_f)jBMM2z&=t#u^o2IE8MX^g^OJV_ef%;;2-T;Fyy^o@8tT|z92InKCBp|P!YXf{--qUcK(PK$UFnl}5CDt0T?5O*xB*VV|^27y=2qVHbnb;BqUTbIwCf z|GPNvbZewI&7w`bMN-w*iGx^vVF8K6sP1dKvPKfAmkFZMrl(#a4h+{_V>=2vDC&+rwm9KP;ff^LMJH4H5!jE)CSHlCgm zn^`m53NEmHL{O1{kGYgiMJeRZ!sprQC8Mw@xl@OZ6ua#(5}+r9 zoRzBNI)49rkZhtkq>qh+t4yfCS)Z4;DV~8jR48cI!Q}o;0Qa;k_f5Ex4^L=_Bg)%Lg8|xdkIfc00{v&rc9)5@_9-Rt+{jeZh8W862aa0<)lgfPguA7 zVA8Y_r7>~Zkm12o?luj1MVD*!Ot~caLA;GLdiiR|NVbej%$V=%l)B}j%acSmDxIAv zKV~`OTPLF;_r)ylZnQ6&7D+(xo{jH!H@y3C)t`$g{1(;ci381;$3B+tV?$9Z$oEOx zrtsCC7_w#~+mp&aN4SJIgLDd^Y-x>0#CqyHLf+YKqC?yy{~AY?qCj-Baj`!`;KS~h zMT0xa@hb|#{srUJPsR^XuRm_Yr~lmV?CgwDvb{a38-P=Kt%#}nCRU4?=A(e|sp|FG z+7;U)%xQJoC9chZR5xi+|EcL#H4RCgr}if~iXo?H+w=Sg=WO zlhrcK5voh>R&yYAC1G7@XlQ4LZF_63o0O`tMz|V{*zxnp70b6fRoUxfODZ{G>h?2* zF}GKF>K#Qz{P!I&RqWo~#RDGt>DRd*->lKlgJUV?OIycBEa;Q3$Cml%T74*W1^B z`3li}U!7V-T^)+p@RT!JZ-kbdS#DSOgWmx&@=F6JT|D_2`$azIP2*P+(&?88 zHbcV0!!v}ZbzRV$f7}vQQBsOtCD@o;`zmydJ4*#MwM8Euv7=0LzE<+V1)U2$^|WM? zLqOb)6kRaRN%==PM+%vT@-+{X4^wwv)?go3N%wW1ncf|(Cw#`0n)vvQ zjFLNuz>S)1#eT_ayY=HMJ2Epg7CZ}7nuD)y#F*`FRXo5)k3*%D(S~*!-621h_z;MR zp|K+DKKaiJSmi^cgcSK{#iWJl&9v>I=JfF2eB=C^bq(n(ZH)e7%x$=1q?8pN{~Cx2 z5e&eT!XFsrYQxo^Bsr@gz1 zj_J8Vw1*)CR@a#meR%Vqtol#u#>Y)kGrMPBBdj?!yGq!#PD zz4w@z5(P<^BW<1~$hb{B51N*t8yGFXF&nzYgioqAzTMuz!IN34cRR$}C@Rl`N}+Fk zI)9OiDKFS4WIJ)5$NA%A*s75iSFE(b&ffCYEhLE>Hb#WuH?<<>Z9WswKWR~GTyEFv zXKd>Emd9vrd1?Q8E@eYx1oh?3l00s+2{ghn?tF%v0nIHI)oW%>0w1d#m}d{dpjfm@ z<)g$nw+OhK%GnY_KRru?GD~{cx)&kdPRfrJTXb7F;V=y0koNJ~ElW9}E5xFzJy~fL zu9`m$aU;el9I;0h2*4fi`iQu<;L>@bZFMC9MKV=n#C_Oz_O5$f*4Dm_v5L#wAgfgS z&DiR!fP2wzXb5kby7sb=w9jOHd`M4CXRY%D%5*SSU^G4<|HFc)@sh#&XbOe&Z-@bx zGnjLoCTzHe7!^EjW9%m7_qo3o$25diDCjFMrm!WrHCP4Q6us;y=Ez%uTBk0Of^~ArjjA0kMqqv?c*^B%@wbJT&LY!6UoYRZ3~m2tQqlJ!^`dQ+bSVBWF z!%60}9$oXvH6hNy&-kC~inSkm#P3 z$GW`rLIDF8vl|!lO0MLCTudpv1a+^+xB-{}m>&lGwjKchFxmSK#=IGY#T| zOL&mgT&wR{oBEim#p3fI(|NJlPFKoyvKkW5ajQ$>m z3NZ%KjFXerbA8|q&w(#o*gOAsrFq^1zWX&yieFkxSV%#UPD=PU0XSR=Sm@^n*elYn zf53ilE$}a0Lm+$nmh|rdmq7!*{5#;^T=jd9!C&bR6Ep}J;h&!Z8)W8aqx;8?1M<`V zMgrODp9^;G=jWB?Ne_&qGl$yhK!F3gn%Z`@7G~PEM!K3hc1EUpzoq{7Y%g!b#oU;AN0AABP4FL>rRtdKM`u8Bppjtt{ClH91+P_DdevV7?#H5En6rrZ3e~+>? zFjnl;TdEoYpicmE!e-&;mF8LD{g&xa5TKQVg|*(lnx3b@+yrf@RO1t1 zx|@KeL?GHu3YaFPZDjtBSQLz`=UFg|sn|x)v;r(lfJF*op{0RYf>3)SU8v3faBLUj z!cfC%`~gSoyaa*V1>vq{fN=`iX27{|F>`$j3Rz%+6`d@h7b7Ehrx~u1dK-l($)h=3qV%*6|aAaM!|lua4ePC zm@5Dc@7%Py)q`n|EzE7LElf?J))X8UvzmI|-wOj$g9$bSLJpEFy%Ee3(6)ifnrhoZ zMXa?g4UKed&I4gS=a2^p8v}&HKPQ}H7Z~`5&%tq$&dm?2rht>g15N@C{eT`Y>z8m8 zN=DW|{3}3h?JUm=2y^6H3-7-K0?^AqyaJx`>q_(F>4yXT*Oe8ZrvGLEDGNO~E|@7n z7pMs+&javDf01F~dt(U9hPm=T{tQf!zLCLsQDL?`Wc4y02XKVvY*{)C#{FSS=8IWE zO$@3(0G2zzA_R%nF$#|b$Nx~r+msXl4c&lgAp@Zh#=)px)AGM$a9%XnjI{VF?=%CV zp#h?Strjr>=7FMv5l(K85lXdm2E^+I!~;syuPe|&PfV9zY%^MGhQ$!FkD3Kd%0=A4Vy6jE*o02VBA0A*#g0Lu?11@ zU<{!GDmwylfoH^V=`RqdY{1J3+PGvB_yEu49ItK#%=>GG;V4>W-6**a*o_n5ft?S3 z4b1zc=tY$q7875VnkPm9)GYu7&hla#VALNm0moBLSXpiB0TvR#BKhSfuy9q|0<%Dl z2Pc&>a-WsM-1xk6sP6m+jgjTzIi1~pvOf=$GY6nIf~{hIUTL11dte;QW*0M*)9@QxJRLC*K>qs6Sc*^^+lyf|M#MKV0H+uL;)(A6uwFYSnmMoNI{a7;DcE($$klTk&D4_ z-{n{=j{%(jITwq(3C8`AK;Y!cuTtFC#efhW0YaPrBwE*PFzeqw=VAqYkr7ck0Ohy= z(p?Zmf)q^oD;_T{RWSSWYZbD<>NLSQC6y__$Up24$5p#YG9Cm0O~mu-5C|nmJX1^JRbool|NgiKN`e;zf}Md zr5&6M?s>yts0C2_d>hNk1jd3w9-ikNnZHPD10<{frkCueh`?)_CnpOS_t(V0Sx5wQ za?}q4;?V%JLJHyuaDsWiW(5|o@H|<&VXXlcWXu2#EZ$RYFisX+7vOmFgj)H`#g*Fh z5ts+I9vp2IR3Oiv0;#Ez{O|f_$p^;$vfg=TfjRT_gqYg2=;gsIAsfJr|1+DwWRopJSak&)xgKDGYhUwIFz>f)a4HO@ zeM_V`P!c`>0RgUkuFt?ISe}F5r^D2|9*L77TzZ1#rPm(%K2; zJ~pz}HT|!|iBX-ast=&H&S`w52aEz~%mk-LvZnGnfNj6905~#`#>IVL++QgKPOZlE zjvAN&crpMFT*_Jo{^I@ZEfjFN%cw-?h5>N7kRoc*1A~Cc^T2FT(!v7_#`w3_g)V?uzheJC+8sCJDzj)nvL3+v;IN=S0n^SUQFz6locb)L z0WjS8O(!%6BFND|iQocPgN22+hNt`@AYc?=x(A@JKtlx6eyRJj08t29Ydc)riNeUA zKeheuN`{^y`X)v*ZYhxl}fk{mUCIK9|{3c-9?}WkxC)If`8oR{}15 zvGIu7gtaE;6URKzaR2G5e>p##UU4HghwmwHx9~BL)xo=;g8R-O;4aFas{S#XFz=PJurs%XpL-?w z4_n#*DbFv5g@eFckd*M8?o;rrEU;w@VFQq)pqQ5m0porZ2|(Tp_rGD;t%z=hHn5;&0FY!L7ZFSP3klwmfKvhagi29i8#-73j}*jHPX_bA z3d5NXekQ7MEFj2CfkF?)>AVHwlz?vES`umxHHDJ}V6HOGM(1^L^I8CGrT#e`zrBv> z0?WZzo9aZ8$AG5iJ-8(c9_xZ63=1rRE~j~p;c6zGYpO^wV=B(-(m z-Lwu(>)+`Dk`w8vAq2he5^x78S_Px|+Z&m|*|h+WRFP{QT>c`!E(( zAzT5|fz^H63Wo~+26%$Y=ETLJ@D|uuw@R=I029ieD`$$nC(At zFpXiGu22dE2P0cu!+*ZNcuJ}eOO7vIlwkLPp&%LI_;K`MjV)}u^nCWj9stw+%AOY# z37Er#&e(Urb`s9>?hgGY4~{oWAzl7_2{0inWWo89edI45U_#K=7zG@al@Y|uF77Mb z$G}LC0WT4HgsvEC@ReMtnuf)d#F z2R9Q$vv67G0|JK9A0~Pg3|MU$$SX9Uys`%@xBrZ?-zxsHpQN^vg`MqrFzoS*p>;Rj zaIqpl7+nzlI+N71f(Y+njDH`%y68EMKZQ*56d=6>5A?@V9sfLg1>^nwWUJpi52{PQ zo^$p0sK0;7_xGrGZ2ye<=U0Azk2-%25Lk!)@4|+hodt>!p2;~0&<62(V literal 0 HcmV?d00001 diff --git a/plugins/ManipulatorScan.form b/plugins/ManipulatorScan.form index 8a402ec..b2dde9b 100755 --- a/plugins/ManipulatorScan.form +++ b/plugins/ManipulatorScan.form @@ -510,6 +510,7 @@ + @@ -528,6 +529,8 @@ + + @@ -556,6 +559,14 @@ + + + + + + + + diff --git a/plugins/ManipulatorScan.java b/plugins/ManipulatorScan.java index ee918a9..7a648ad 100755 --- a/plugins/ManipulatorScan.java +++ b/plugins/ManipulatorScan.java @@ -1,15 +1,19 @@ /* * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved. - */ + */ import ch.psi.pshell.device.Motor; import ch.psi.pshell.ui.Panel; +import ch.psi.pshell.ui.ScriptProcessor; import ch.psi.pshell.ui.Plugin; +import ch.psi.pshell.ui.QueueProcessor; import ch.psi.utils.State; import ch.psi.utils.swing.SwingUtils; import java.awt.Component; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JCheckBox; @@ -18,7 +22,7 @@ import javax.swing.SpinnerNumberModel; /** * */ -public class ManipulatorScan extends Panel { +public class ManipulatorScan extends ScriptProcessor { public ManipulatorScan() { initComponents(); @@ -64,8 +68,14 @@ public class ManipulatorScan extends Panel { void updateTable(){ } - - void startScan() throws Exception { + + @Override + public String getScript() { + return "ManipulatorScan"; + } + + @Override + public Map getArgs(){ HashMap args = new HashMap<>(); args.put("MOTOR", comboMotor.getSelectedItem().toString()); ArrayList sensors = new ArrayList(); @@ -89,45 +99,10 @@ public class ManipulatorScan extends Panel { } args.put("LATENCY", (Double) spinnerLatency.getValue()); args.put("RELATIVE", radioRelative.isSelected()); - - runAsync("ManipulatorScan", args); - - /* - getContext().setExecutingContext("manip_scan"); - String scan ="lscan(" + comboMotor.getSelectedItem().toString() + ", ( " ; - for (Component c : panelSensors.getComponents()) { - if ((c instanceof JCheckBox) && ((JCheckBox)c).isSelected()) - scan += c.getName() + ", "; - } - scan+="), "; - if (radioRelative.isSelected()){ - double halfRange = (Double)spinnerRange.getValue()/2; - scan+= (-halfRange) + ", " + (halfRange)+ ", "; - } else { - Double from = (Double)spinnerFrom.getValue(); - Double to = (Double)spinnerTo.getValue(); - - if (to <= from){ - throw new Exception ("Invalid range"); - } - scan+= from + ", " + to + ", "; - } - - scan+= (radioStepSize.isSelected()) ? "(" +((Double)spinnerStepSize.getValue())+",)" :((Integer)spinnerSteps.getValue()); - scan+=", " + (Double)spinnerLatency.getValue() + ", "; - if (radioRelative.isSelected()){ - scan+="True, "; //Relative - } - scan+="before_read=trig_scienta)"; //Relative - - if (checkImageIntegration.isSelected()){ - scan = "set_preference(Preference.PLOT_TYPES,{'integration':1}); " + scan; - } - - evalAsync(scan); - */ + return args; } + @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { @@ -173,6 +148,7 @@ public class ManipulatorScan extends Panel { buttonStart = new javax.swing.JButton(); buttonAbort = new javax.swing.JButton(); jButton1 = new javax.swing.JButton(); + buttonAddToQueue = new javax.swing.JButton(); panelPositioner.setBorder(javax.swing.BorderFactory.createTitledBorder("Positioner")); @@ -473,6 +449,13 @@ public class ManipulatorScan extends Panel { jButton1.setText("jButton1"); + buttonAddToQueue.setText("Add To Queue"); + buttonAddToQueue.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonAddToQueueActionPerformed(evt); + } + }); + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); jPanel3.setLayout(jPanel3Layout); jPanel3Layout.setHorizontalGroup( @@ -481,7 +464,8 @@ public class ManipulatorScan extends Panel { .addContainerGap() .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(buttonStart, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(buttonAbort, javax.swing.GroupLayout.DEFAULT_SIZE, 244, Short.MAX_VALUE)) + .addComponent(buttonAbort, javax.swing.GroupLayout.DEFAULT_SIZE, 244, Short.MAX_VALUE) + .addComponent(buttonAddToQueue, javax.swing.GroupLayout.DEFAULT_SIZE, 244, Short.MAX_VALUE)) .addContainerGap()) .addGroup(jPanel3Layout.createSequentialGroup() .addGap(21, 21, 21) @@ -496,6 +480,8 @@ public class ManipulatorScan extends Panel { .addGap(18, 18, 18) .addComponent(buttonAbort) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonAddToQueue) + .addGap(36, 36, 36) .addComponent(jButton1) .addGap(143, 143, 143)) ); @@ -538,7 +524,7 @@ public class ManipulatorScan extends Panel { private void buttonStartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonStartActionPerformed try { - startScan(); + execute(); } catch (Exception ex) { SwingUtils.showException(this, ex); } @@ -566,7 +552,7 @@ public class ManipulatorScan extends Panel { //spinnerFrom.setModel(new javax.swing.SpinnerNumberModel(3.0, 0.001, 100.0, 1.0)); //spinnerTo.setModel(new javax.swing.SpinnerNumberModel(2.0, 0.001, 100.0, 1.0)); } catch (Exception ex) { - SwingUtils.showException(this, ex); + showException(ex); } }//GEN-LAST:event_comboMotorActionPerformed @@ -574,8 +560,20 @@ public class ManipulatorScan extends Panel { setEnabled(isEnabled()); }//GEN-LAST:event_radioAbsoluteActionPerformed + private void buttonAddToQueueActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonAddToQueueActionPerformed + try { + List queues = getView().getQueues(); + QueueProcessor tq = (queues.size() == 0) ? getView().openProcessor(QueueProcessor.class, null) : queues.get(0); + getView().getDocumentsTab().setSelectedComponent(tq); + tq.addNewFile(getScript(), getArgs()); + } catch (Exception ex) { + showException( ex); + } + }//GEN-LAST:event_buttonAddToQueueActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton buttonAbort; + private javax.swing.JButton buttonAddToQueue; private javax.swing.ButtonGroup buttonGroup1; private javax.swing.ButtonGroup buttonGroup2; private javax.swing.JButton buttonScientaSetup; @@ -617,4 +615,5 @@ public class ManipulatorScan extends Panel { private javax.swing.JSpinner spinnerTo; private javax.swing.JLabel txtSize; // End of variables declaration//GEN-END:variables + } diff --git a/plugins/PanelPlugin.form b/plugins/PanelPlugin.form index 0fbdc12..c52670e 100755 --- a/plugins/PanelPlugin.form +++ b/plugins/PanelPlugin.form @@ -22,39 +22,51 @@ - + - - - - - - + + + + + + + + + + + + + + + + - - - + - - + + + + + + - - - - + - - + + + + + - + @@ -62,10 +74,7 @@ - - - - + @@ -73,17 +82,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + @@ -166,5 +195,19 @@ + + + + + + + + + + + + + + diff --git a/plugins/PanelPlugin.java b/plugins/PanelPlugin.java index b40099e..52e9fab 100755 --- a/plugins/PanelPlugin.java +++ b/plugins/PanelPlugin.java @@ -3,9 +3,14 @@ */ import ch.psi.pshell.core.Context; +import ch.psi.pshell.epics.ChannelDouble; +import ch.psi.pshell.epics.GenericChannel; +import ch.psi.pshell.plot.Plot; +import ch.psi.pshell.swing.DevicePanel; import ch.psi.pshell.ui.Panel; import ch.psi.utils.State; import java.awt.Component; +import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JButton; @@ -18,13 +23,29 @@ public class PanelPlugin extends Panel { public PanelPlugin() { initComponents(); this.setPersistedComponents(new Component[]{jRadioButton1, jRadioButton2}); + line.getAxis(Plot.AxisId.Y).setLabel("LinePlot"); + time.getAxis(Plot.AxisId.Y).setLabel("TimePlot"); + time.setTitle("Test"); + } //Overridables + @Override public void onInitialize(int runCount) { - + DevicePanel[] panels = new DevicePanel[]{panelSetp, panelRbck}; + for (DevicePanel p : panels){ + GenericChannel channel = new GenericChannel(p.getName(), p.getName(), 3); + channel.setMonitored(true); + try { + channel.initialize(); + p.setDevice(channel); + } catch (Exception ex) { + Logger.getLogger(PanelPlugin.class.getName()).log(Level.SEVERE, null, ex); + } + } } + @Override public void onStateChange(State state, State former) { @@ -56,6 +77,10 @@ public class PanelPlugin extends Panel { jRadioButton2 = new javax.swing.JRadioButton(); buttonExecShellCmd = new javax.swing.JButton(); jButton2 = new javax.swing.JButton(); + line = new ch.psi.pshell.plot.LinePlotJFree(); + time = new ch.psi.pshell.plot.TimePlotJFree(); + panelSetp = new ch.psi.pshell.swing.RegisterPanel(); + panelRbck = new ch.psi.pshell.swing.DeviceValuePanel(); motorPanel2.setDeviceName("m1"); @@ -90,6 +115,10 @@ public class PanelPlugin extends Panel { jButton2.setText("Exec Sync"); + panelSetp.setName("TESTIOC:TESTCALCOUT:Input"); // NOI18N + + panelRbck.setName("TESTIOC:TESTCALCOUT:Input"); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -100,52 +129,76 @@ public class PanelPlugin extends Panel { .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(scriptButton2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(jSpinner1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(motorPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jSpinner2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jSpinner3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(scriptButton1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(jSpinner1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(motorPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jSpinner2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(buttonExecShellCmd) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jRadioButton2) + .addComponent(jRadioButton1))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButton2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSpinner3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))) .addGroup(layout.createSequentialGroup() - .addGap(36, 36, 36) + .addGap(86, 86, 86) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jRadioButton2) - .addComponent(jRadioButton1)))) - .addContainerGap(168, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(panelSetp, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelRbck, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(scriptButton1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jButton2) - .addComponent(buttonExecShellCmd)) - .addGap(225, 225, 225)) + .addComponent(line, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addGap(6, 6, 6) + .addComponent(time, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(scriptButton1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(motorPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(motorPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(scriptButton2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jSpinner1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jSpinner2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSpinner3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(jRadioButton1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jRadioButton2) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonExecShellCmd) + .addComponent(jButton2)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panelSetp, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(panelRbck, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(44, 44, 44) + .addComponent(scriptButton1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(25, 25, 25)) + .addGroup(layout.createSequentialGroup() + .addComponent(line, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jSpinner3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(jRadioButton1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jRadioButton2) - .addGap(18, 18, 18) - .addComponent(buttonExecShellCmd) - .addGap(18, 18, 18) - .addComponent(jButton2) - .addContainerGap(35, Short.MAX_VALUE)) + .addComponent(time, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 12, Short.MAX_VALUE)) ); + + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {panelRbck, panelSetp}); + }// //GEN-END:initComponents private void buttonExecShellCmdActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonExecShellCmdActionPerformed @@ -178,8 +231,12 @@ public class PanelPlugin extends Panel { private javax.swing.JSpinner jSpinner1; private javax.swing.JSpinner jSpinner2; private javax.swing.JSpinner jSpinner3; + private ch.psi.pshell.plot.LinePlotJFree line; private ch.psi.pshell.swing.MotorPanel motorPanel2; + private ch.psi.pshell.swing.DeviceValuePanel panelRbck; + private ch.psi.pshell.swing.RegisterPanel panelSetp; private ch.psi.pshell.swing.ScriptButton scriptButton1; private ch.psi.pshell.swing.ScriptButton scriptButton2; + private ch.psi.pshell.plot.TimePlotJFree time; // End of variables declaration//GEN-END:variables } diff --git a/plugins/README b/plugins/README new file mode 100644 index 0000000..501c79a --- /dev/null +++ b/plugins/README @@ -0,0 +1,5 @@ +This plugin for the analysis of PEEM images requires the following additional +libraries: +JTransform, http://sites.google.com/site/piotrwendykier/software/jtransforms +Michael Thomas Flanagan's Java Scientific Library, http://www.ee.ucl.ac.uk/~mflanaga/java/ +JMatIO, http://www.mathworks.com/matlabcentral/fileexchange/10759 diff --git a/plugins/Regine.form b/plugins/Regine.form index 027950d..73438ee 100644 --- a/plugins/Regine.form +++ b/plugins/Regine.form @@ -297,6 +297,9 @@ + + + diff --git a/plugins/Regine.java b/plugins/Regine.java index dd86ad4..c759858 100644 --- a/plugins/Regine.java +++ b/plugins/Regine.java @@ -212,6 +212,8 @@ public class Regine extends ScriptProcessor { jLabel11.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); jLabel11.setText("Exposures(s):"); + checkZigZag.setSelected(true); + jLabel12.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); jLabel12.setText("ZigZag:"); diff --git a/plugins/SIStem.form b/plugins/SIStem.form new file mode 100644 index 0000000..18399ae --- /dev/null +++ b/plugins/SIStem.form @@ -0,0 +1,395 @@ + + +